diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml deleted file mode 100644 index af2d2e8f4e..0000000000 --- a/.haml-lint_todo.yml +++ /dev/null @@ -1,13 +0,0 @@ -# This configuration was generated by -# `haml-lint --auto-gen-config` -# on 2024-01-09 11:30:07 -0500 using Haml-Lint version 0.53.0. -# The point is for the user to remove these configuration records -# one by one as the lints are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of Haml-Lint, may require this file to be generated again. - -linters: - # Offense count: 1 - LineLength: - exclude: - - 'app/views/admin/roles/_form.html.haml' diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb index 388d4b9e1d..c2563c492e 100644 --- a/app/controllers/activitypub/base_controller.rb +++ b/app/controllers/activitypub/base_controller.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class ActivityPub::BaseController < Api::BaseController + include SignatureVerification + include AccountOwnedConcern + skip_before_action :require_authenticated_user! skip_before_action :require_not_suspended! skip_around_action :set_locale diff --git a/app/controllers/activitypub/claims_controller.rb b/app/controllers/activitypub/claims_controller.rb index 339333e462..480baaf2bc 100644 --- a/app/controllers/activitypub/claims_controller.rb +++ b/app/controllers/activitypub/claims_controller.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ActivityPub::ClaimsController < ActivityPub::BaseController - include SignatureVerification - include AccountOwnedConcern - skip_before_action :authenticate_user! before_action :require_account_signature! diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 4ed59388ff..57480db8d8 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ActivityPub::CollectionsController < ActivityPub::BaseController - include SignatureVerification - include AccountOwnedConcern - vary_by -> { 'Signature' if authorized_fetch_mode? } before_action :require_account_signature!, if: :authorized_fetch_mode? diff --git a/app/controllers/activitypub/followers_synchronizations_controller.rb b/app/controllers/activitypub/followers_synchronizations_controller.rb index d2942104e5..392dd36bcd 100644 --- a/app/controllers/activitypub/followers_synchronizations_controller.rb +++ b/app/controllers/activitypub/followers_synchronizations_controller.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController - include SignatureVerification - include AccountOwnedConcern - vary_by -> { 'Signature' if authorized_fetch_mode? } before_action :require_account_signature! diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb index e8b0f47cde..49cfc8ad1c 100644 --- a/app/controllers/activitypub/inboxes_controller.rb +++ b/app/controllers/activitypub/inboxes_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class ActivityPub::InboxesController < ActivityPub::BaseController - include SignatureVerification include JsonLdHelper - include AccountOwnedConcern before_action :skip_unknown_actor_activity before_action :require_actor_signature! diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index bf10ba762a..8079e011dd 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -3,9 +3,6 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController LIMIT = 20 - include SignatureVerification - include AccountOwnedConcern - vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? } before_action :require_account_signature!, if: :authorized_fetch_mode? diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index c38ff89d1c..3f43e89a5e 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class ActivityPub::RepliesController < ActivityPub::BaseController - include SignatureVerification include Authorization - include AccountOwnedConcern DESCENDANTS_LIMIT = 60 diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb index 8422d74bc3..f2b1eaa3e7 100644 --- a/app/controllers/instance_actors_controller.rb +++ b/app/controllers/instance_actors_controller.rb @@ -6,6 +6,8 @@ class InstanceActorsController < ActivityPub::BaseController serialization_scope nil before_action :set_account + + skip_before_action :authenticate_user! # From `AccountOwnedConcern` skip_before_action :require_functional! skip_before_action :update_user_sign_in @@ -16,6 +18,11 @@ class InstanceActorsController < ActivityPub::BaseController private + # Skips various `before_action` from `AccountOwnedConcern` + def account_required? + false + end + def set_account @account = Account.representative end diff --git a/app/javascript/core/embed.js b/app/javascript/core/embed.js deleted file mode 100644 index d1e8f6b108..0000000000 --- a/app/javascript/core/embed.js +++ /dev/null @@ -1,25 +0,0 @@ -// This file will be loaded on embed pages, regardless of theme. - -import 'packs/public-path'; - -window.addEventListener('message', e => { - const data = e.data || {}; - - if (!window.parent || data.type !== 'setHeight') { - return; - } - - function setEmbedHeight () { - window.parent.postMessage({ - type: 'setHeight', - id: data.id, - height: document.getElementsByTagName('html')[0].scrollHeight, - }, '*'); - } - - if (['interactive', 'complete'].includes(document.readyState)) { - setEmbedHeight(); - } else { - document.addEventListener('DOMContentLoaded', setEmbedHeight); - } -}); diff --git a/app/javascript/core/embed.ts b/app/javascript/core/embed.ts new file mode 100644 index 0000000000..6766cd7788 --- /dev/null +++ b/app/javascript/core/embed.ts @@ -0,0 +1,41 @@ +// This file will be loaded on embed pages, regardless of theme. + +import 'packs/public-path'; +import ready from '../mastodon/ready'; + +interface SetHeightMessage { + type: 'setHeight'; + id: string; + height: number; +} + +function isSetHeightMessage(data: unknown): data is SetHeightMessage { + if ( + data && + typeof data === 'object' && + 'type' in data && + data.type === 'setHeight' + ) + return true; + else return false; +} + +window.addEventListener('message', (e) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases + if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return; + + const data = e.data; + + ready(() => { + window.parent.postMessage( + { + type: 'setHeight', + id: data.id, + height: document.getElementsByTagName('html')[0].scrollHeight, + }, + '*', + ); + }).catch((e) => { + console.error('Error in setHeightMessage postMessage', e); + }); +}); diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js deleted file mode 100644 index 23367d2d31..0000000000 --- a/app/javascript/core/settings.js +++ /dev/null @@ -1,44 +0,0 @@ -// This file will be loaded on settings pages, regardless of theme. - -import 'packs/public-path'; -import Rails from '@rails/ujs'; - -Rails.delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => { - const avatar = document.getElementById(target.id + '-preview'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; - - avatar.src = url; -}); - -Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { - target.focus(); - target.select(); - target.setSelectionRange(0, target.value.length); -}); - -Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { - const input = target.parentNode.querySelector('.input-copy__wrapper input'); - - const oldReadOnly = input.readonly; - - input.readonly = false; - input.focus(); - input.select(); - input.setSelectionRange(0, input.value.length); - - try { - if (document.execCommand('copy')) { - input.blur(); - target.parentNode.classList.add('copied'); - - setTimeout(() => { - target.parentNode.classList.remove('copied'); - }, 700); - } - } catch (err) { - console.error(err); - } - - input.readonly = oldReadOnly; -}); diff --git a/app/javascript/core/settings.ts b/app/javascript/core/settings.ts new file mode 100644 index 0000000000..ea6a99ec80 --- /dev/null +++ b/app/javascript/core/settings.ts @@ -0,0 +1,70 @@ +// This file will be loaded on settings pages, regardless of theme. + +import 'packs/public-path'; +import Rails from '@rails/ujs'; + +Rails.delegate( + document, + '#edit_profile input[type=file]', + 'change', + ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + const avatar = document.querySelector( + `img#${target.id}-preview`, + ); + + if (!avatar) return; + + let file: File | undefined; + if (target.files) file = target.files[0]; + + const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + + if (url) avatar.src = url; + }, +); + +Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + target.focus(); + target.select(); + target.setSelectionRange(0, target.value.length); +}); + +Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { + if (!(target instanceof HTMLButtonElement)) return; + + const input = target.parentNode?.querySelector( + '.input-copy__wrapper input', + ); + + if (!input) return; + + const oldReadOnly = input.readOnly; + + input.readOnly = false; + input.focus(); + input.select(); + input.setSelectionRange(0, input.value.length); + + try { + if (document.execCommand('copy')) { + input.blur(); + + const parent = target.parentElement; + + if (!parent) return; + parent.classList.add('copied'); + + setTimeout(() => { + parent.classList.remove('copied'); + }, 700); + } + } catch (err) { + console.error(err); + } + + input.readOnly = oldReadOnly; +}); diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml index ab8de6afad..12c23e2035 100644 --- a/app/javascript/core/theme.yml +++ b/app/javascript/core/theme.yml @@ -7,7 +7,7 @@ pack: common: filename: common.js stylesheet: true - embed: embed.js + embed: embed.ts error: home: inert: @@ -18,7 +18,7 @@ pack: stylesheet: true modal: public: - settings: settings.js + settings: settings.ts sign_up: share: remote_interaction_helper: remote_interaction_helper.ts diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js index e293657ad3..54296d0905 100644 --- a/app/javascript/flavours/glitch/actions/blocks.js +++ b/app/javascript/flavours/glitch/actions/blocks.js @@ -12,8 +12,6 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; -export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL'; - export function fetchBlocks() { return (dispatch, getState) => { dispatch(fetchBlocksRequest()); @@ -90,11 +88,12 @@ export function expandBlocksFail(error) { export function initBlockModal(account) { return dispatch => { - dispatch({ - type: BLOCKS_INIT_MODAL, - account, - }); - - dispatch(openModal({ modalType: 'BLOCK' })); + dispatch(openModal({ + modalType: 'BLOCK', + modalProps: { + accountId: account.get('id'), + acct: account.get('acct'), + }, + })); }; } diff --git a/app/javascript/flavours/glitch/actions/domain_blocks.js b/app/javascript/flavours/glitch/actions/domain_blocks.js index 718002613f..55c0a6ce9d 100644 --- a/app/javascript/flavours/glitch/actions/domain_blocks.js +++ b/app/javascript/flavours/glitch/actions/domain_blocks.js @@ -1,6 +1,8 @@ import api, { getLinks } from '../api'; import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed"; +import { openModal } from './modal'; + export * from "./domain_blocks_typed"; @@ -150,3 +152,12 @@ export function expandDomainBlocksFail(error) { error, }; } + +export const initDomainBlockModal = account => dispatch => dispatch(openModal({ + modalType: 'DOMAIN_BLOCK', + modalProps: { + domain: account.get('acct').split('@')[1], + acct: account.get('acct'), + accountId: account.get('id'), + }, +})); diff --git a/app/javascript/flavours/glitch/actions/mutes.js b/app/javascript/flavours/glitch/actions/mutes.js index fb041078b8..99c113f414 100644 --- a/app/javascript/flavours/glitch/actions/mutes.js +++ b/app/javascript/flavours/glitch/actions/mutes.js @@ -12,10 +12,6 @@ export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; -export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; -export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; -export const MUTES_CHANGE_DURATION = 'MUTES_CHANGE_DURATION'; - export function fetchMutes() { return (dispatch, getState) => { dispatch(fetchMutesRequest()); @@ -92,26 +88,12 @@ export function expandMutesFail(error) { export function initMuteModal(account) { return dispatch => { - dispatch({ - type: MUTES_INIT_MODAL, - account, - }); - - dispatch(openModal({ modalType: 'MUTE' })); - }; -} - -export function toggleHideNotifications() { - return dispatch => { - dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS }); - }; -} - -export function changeMuteDuration(duration) { - return dispatch => { - dispatch({ - type: MUTES_CHANGE_DURATION, - duration, - }); + dispatch(openModal({ + modalType: 'MUTE', + modalProps: { + accountId: account.get('id'), + acct: account.get('acct'), + }, + })); }; } diff --git a/app/javascript/flavours/glitch/components/check_box.tsx b/app/javascript/flavours/glitch/components/check_box.tsx new file mode 100644 index 0000000000..7da8ef0ac5 --- /dev/null +++ b/app/javascript/flavours/glitch/components/check_box.tsx @@ -0,0 +1,39 @@ +import classNames from 'classnames'; + +import DoneIcon from '@/material-icons/400-24px/done.svg?react'; + +import { Icon } from './icon'; + +interface Props { + value: string; + checked: boolean; + name: string; + onChange: (event: React.ChangeEvent) => void; + label: React.ReactNode; +} + +export const CheckBox: React.FC = ({ + name, + value, + checked, + onChange, + label, +}) => { + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/components/relative_timestamp.tsx b/app/javascript/flavours/glitch/components/relative_timestamp.tsx index 12530c2b17..b9e1e4f8fd 100644 --- a/app/javascript/flavours/glitch/components/relative_timestamp.tsx +++ b/app/javascript/flavours/glitch/components/relative_timestamp.tsx @@ -102,7 +102,7 @@ const getUnitDelay = (units: string) => { }; export const timeAgoString = ( - intl: IntlShape, + intl: Pick, date: Date, now: number, year: number, diff --git a/app/javascript/flavours/glitch/features/account/components/domain_pill.jsx b/app/javascript/flavours/glitch/features/account/components/domain_pill.jsx new file mode 100644 index 0000000000..9cd028fa68 --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/components/domain_pill.jsx @@ -0,0 +1,86 @@ +import PropTypes from 'prop-types'; +import { useState, useRef, useCallback } 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 = ({ domain, username, isSelf }) => { + 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 ( + <> + + + + {({ props }) => ( +
+
+
+

+
+ +
+
{isSelf ? : }
+
@{username}@{domain}
+
+ +
+
+
+ +
+
+

{isSelf ? : }

+
+
+ +
+
+ +
+
+

{isSelf ? : }

+
+
+
+ +

{isSelf ? }} /> : }} />}

+ + {expanded && ( + <> +

+

+ + )} +
+ )} +
+ + ); +}; + +DomainPill.propTypes = { + username: PropTypes.string.isRequired, + domain: PropTypes.string.isRequired, + isSelf: PropTypes.bool, +}; diff --git a/app/javascript/flavours/glitch/features/account/components/header.jsx b/app/javascript/flavours/glitch/features/account/components/header.jsx index c693e85f41..b97de5aeab 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/header.jsx @@ -23,7 +23,7 @@ import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; -import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state'; +import { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -31,6 +31,8 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import AccountNoteContainer from '../containers/account_note_container'; import FollowRequestNoteContainer from '../containers/follow_request_note_container'; +import { DomainPill } from './domain_pill'; + const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -75,7 +77,7 @@ const messages = defineMessages({ const titleFromAccount = account => { const displayName = account.get('display_name'); - const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct'); + const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${localDomain}` : account.get('acct'); const prefix = displayName.trim().length === 0 ? account.get('username') : displayName; return `${prefix} (@${acct})`; @@ -167,7 +169,7 @@ class Header extends ImmutablePureComponent { }; render () { - const { account, hidden, intl, domain } = this.props; + const { account, hidden, intl } = this.props; const { signedIn, permissions } = this.context.identity; if (!account) { @@ -314,7 +316,8 @@ class Header extends ImmutablePureComponent { const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); const isLocal = account.get('acct').indexOf('@') === -1; - const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const username = account.get('acct').split('@')[0]; + const domain = isLocal ? localDomain : account.get('acct').split('@')[1]; const isIndexable = !account.get('noindex'); const badges = []; @@ -359,7 +362,9 @@ class Header extends ImmutablePureComponent {

- @{acct} {lockedIcon} + @{username}@{domain} + + {lockedIcon}

diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx b/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx index eb2ddbdd80..37258d9d83 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx @@ -72,11 +72,7 @@ class Header extends ImmutablePureComponent { }; handleBlockDomain = () => { - const domain = this.props.account.get('acct').split('@')[1]; - - if (!domain) return; - - this.props.onBlockDomain(domain); + this.props.onBlockDomain(this.props.account); }; handleUnblockDomain = () => { diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx index c3a3de71ed..d42bb2c251 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx @@ -15,7 +15,7 @@ import { mentionCompose, directCompose, } from '../../../actions/compose'; -import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; +import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blocks'; import { openModal } from '../../../actions/modal'; import { initMuteModal } from '../../../actions/mutes'; import { initReport } from '../../../actions/reports'; @@ -138,15 +138,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onBlockDomain (domain) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), - }, - })); + onBlockDomain (account) { + dispatch(initDomainBlockModal(account)); }, onUnblockDomain (domain) { diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx index d27d134b30..585abb8b6c 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -11,6 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; @@ -22,7 +23,6 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; -import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { fetchLists } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; @@ -158,7 +158,7 @@ class GettingStarted extends ImmutablePureComponent { } if (showTrends) { - navItems.push(); + navItems.push(); } if (signedIn) { diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx index cfac692324..fa772b067d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx @@ -1,100 +1,116 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useCallback, useState } from 'react'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; +import classNames from 'classnames'; -import { blockAccount } from '../../../actions/accounts'; -import { closeModal } from '../../../actions/modal'; -import { initReport } from '../../../actions/reports'; -import { Button } from '../../../components/button'; -import { makeGetAccount } from '../../../selectors'; +import { useDispatch } from 'react-redux'; -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import BlockIcon from '@/material-icons/400-24px/block.svg?react'; +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { blockAccount } from 'flavours/glitch/actions/accounts'; +import { closeModal } from 'flavours/glitch/actions/modal'; +import { Button } from 'flavours/glitch/components/button'; +import { Icon } from 'flavours/glitch/components/icon'; - const mapStateToProps = state => ({ - account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), - }); +export const BlockModal = ({ accountId, acct }) => { + const dispatch = useDispatch(); + const [expanded, setExpanded] = useState(false); - return mapStateToProps; -}; + const domain = acct.split('@')[1]; -const mapDispatchToProps = dispatch => { - return { - onConfirm(account) { - dispatch(blockAccount(account.get('id'))); - }, + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockAccount(accountId)); + }, [dispatch, accountId]); - onBlockAndReport(account) { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account)); - }, + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); - onClose() { - dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })); - }, - }; -}; + const handleToggleLearnMore = useCallback(() => { + setExpanded(!expanded); + }, [expanded, setExpanded]); -class BlockModal extends PureComponent { + return ( +
+
+
+
+ +
- static propTypes = { - account: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - onBlockAndReport: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleClick = () => { - this.props.onClose(); - this.props.onConfirm(this.props.account); - }; - - handleSecondary = () => { - this.props.onClose(); - this.props.onBlockAndReport(this.props.account); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - render () { - const { account } = this.props; - - return ( -
-
-

- @{account.get('acct')} }} - /> -

+
+

+
@{acct}
+
-
-
+ +
+ {domain && ( +
+
+ {domain} }} + /> +
+
+ )} + +
+ {domain && ( + + )} + +
+ + - - + +
- ); - } +
+ ); +}; -} +BlockModal.propTypes = { + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; -export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(BlockModal)); +export default BlockModal; diff --git a/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.jsx new file mode 100644 index 0000000000..b1ab81dab5 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/domain_block_modal.jsx @@ -0,0 +1,106 @@ +import PropTypes from 'prop-types'; +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { useDispatch } from 'react-redux'; + +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react'; +import HistoryIcon from '@/material-icons/400-24px/history.svg?react'; +import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { blockAccount } from 'flavours/glitch/actions/accounts'; +import { blockDomain } from 'flavours/glitch/actions/domain_blocks'; +import { closeModal } from 'flavours/glitch/actions/modal'; +import { Button } from 'flavours/glitch/components/button'; +import { Icon } from 'flavours/glitch/components/icon'; + +export const DomainBlockModal = ({ domain, accountId, acct }) => { + const dispatch = useDispatch(); + + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockDomain(domain)); + }, [dispatch, domain]); + + const handleSecondaryClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockAccount(accountId)); + }, [dispatch, accountId]); + + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); + + return ( +
+
+
+
+ +
+ +
+

+
{domain}
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+ + +
+ + + + +
+
+
+ ); +}; + +DomainBlockModal.propTypes = { + domain: PropTypes.string.isRequired, + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; + +export default DomainBlockModal; diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx index 4ecf07b030..3f7e291e7b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx @@ -7,6 +7,7 @@ import Base from 'flavours/glitch/components/modal_root'; import { MuteModal, BlockModal, + DomainBlockModal, ReportModal, SettingsModal, EmbedModal, @@ -48,6 +49,7 @@ export const MODAL_COMPONENTS = { 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'MUTE': MuteModal, 'BLOCK': BlockModal, + 'DOMAIN_BLOCK': DomainBlockModal, 'REPORT': ReportModal, 'SETTINGS': SettingsModal, 'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }), diff --git a/app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx index fa81ea81a3..a161e24861 100644 --- a/app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx @@ -1,138 +1,154 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useCallback, useState } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; +import classNames from 'classnames'; -import Toggle from 'react-toggle'; +import { useDispatch } from 'react-redux'; -import { muteAccount } from '../../../actions/accounts'; -import { closeModal } from '../../../actions/modal'; -import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes'; -import { Button } from '../../../components/button'; + +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; +import { muteAccount } from 'flavours/glitch/actions/accounts'; +import { closeModal } from 'flavours/glitch/actions/modal'; +import { Button } from 'flavours/glitch/components/button'; +import { CheckBox } from 'flavours/glitch/components/check_box'; +import { Icon } from 'flavours/glitch/components/icon'; +import { RadioButton } from 'flavours/glitch/components/radio_button'; const messages = defineMessages({ minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' }, hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' }, days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' }, - indefinite: { id: 'mute_modal.indefinite', defaultMessage: 'Indefinite' }, + indefinite: { id: 'mute_modal.indefinite', defaultMessage: 'Until I unmute them' }, + hideFromNotifications: { id: 'mute_modal.hide_from_notifications', defaultMessage: 'Hide from notifications' }, }); -const mapStateToProps = state => { - return { - account: state.getIn(['mutes', 'new', 'account']), - notifications: state.getIn(['mutes', 'new', 'notifications']), - muteDuration: state.getIn(['mutes', 'new', 'duration']), - }; +const RadioButtonLabel = ({ name, value, currentValue, onChange, label }) => ( + +); + +RadioButtonLabel.propTypes = { + name: PropTypes.string, + value: PropTypes.oneOf([PropTypes.string, PropTypes.number, PropTypes.bool]), + currentValue: PropTypes.oneOf([PropTypes.string, PropTypes.number, PropTypes.bool]), + checked: PropTypes.bool, + onChange: PropTypes.func, + label: PropTypes.node, }; -const mapDispatchToProps = dispatch => { - return { - onConfirm(account, notifications, muteDuration) { - dispatch(muteAccount(account.get('id'), notifications, muteDuration)); - }, +export const MuteModal = ({ accountId, acct }) => { + const intl = useIntl(); + const dispatch = useDispatch(); + const [notifications, setNotifications] = useState(true); + const [muteDuration, setMuteDuration] = useState('0'); + const [expanded, setExpanded] = useState(false); - onClose() { - dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })); - }, + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(muteAccount(accountId, notifications, muteDuration)); + }, [dispatch, accountId, notifications, muteDuration]); - onToggleNotifications() { - dispatch(toggleHideNotifications()); - }, + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); - onChangeMuteDuration(e) { - dispatch(changeMuteDuration(e.target.value)); - }, - }; -}; + const handleToggleNotifications = useCallback(({ target }) => { + setNotifications(target.checked); + }, [setNotifications]); -class MuteModal extends PureComponent { + const handleChangeMuteDuration = useCallback(({ target }) => { + setMuteDuration(target.value); + }, [setMuteDuration]); - static propTypes = { - account: PropTypes.object.isRequired, - notifications: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - onToggleNotifications: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - muteDuration: PropTypes.number.isRequired, - onChangeMuteDuration: PropTypes.func.isRequired, - }; + const handleToggleSettings = useCallback(() => { + setExpanded(!expanded); + }, [expanded, setExpanded]); - handleClick = () => { - this.props.onClose(); - this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - toggleNotifications = () => { - this.props.onToggleNotifications(); - }; - - changeMuteDuration = (e) => { - this.props.onChangeMuteDuration(e); - }; - - render () { - const { account, notifications, muteDuration, intl } = this.props; - - return ( -
-
-

- @{account.get('acct')} }} - /> -

-

- -

-
- - + return ( +
+
+
+
+
-
- : - +
+

+
@{acct}
-
-
+ +
+
+
+ + + + +
+ +
+ +
+
+ +
+ + +
+ + - + +
- ); - } +
+ ); +}; -} +MuteModal.propTypes = { + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(MuteModal)); +export default MuteModal; diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index 14d46d7043..6a140e3fd7 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -126,6 +126,10 @@ export function BlockModal () { return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'../components/block_modal'); } +export function DomainBlockModal () { + return import(/* webpackChunkName: "flavours/glitch/async/modals/domain_block_modal" */'../components/domain_block_modal'); +} + export function ReportModal () { return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'../components/report_modal'); } diff --git a/app/javascript/flavours/glitch/packs/public.jsx b/app/javascript/flavours/glitch/packs/public.jsx deleted file mode 100644 index dab33b3904..0000000000 --- a/app/javascript/flavours/glitch/packs/public.jsx +++ /dev/null @@ -1,241 +0,0 @@ -import 'packs/public-path'; -import { createRoot } from 'react-dom/client'; - -import { IntlMessageFormat } from 'intl-messageformat'; -import { defineMessages } from 'react-intl'; - -import Rails from '@rails/ujs'; -import axios from 'axios'; -import { createBrowserHistory } from 'history'; -import { throttle } from 'lodash'; - -import { timeAgoString } from 'flavours/glitch/components/relative_timestamp'; -import emojify from 'flavours/glitch/features/emoji/emoji'; -import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; -import { loadLocale, getLocale } from 'flavours/glitch/locales'; -import { loadPolyfills } from 'flavours/glitch/polyfills'; - -const messages = defineMessages({ - usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' }, - passwordExceedsLength: { id: 'password_confirmation.exceeds_maxlength', defaultMessage: 'Password confirmation exceeds the maximum password length' }, - passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' }, -}); - -function main() { - const { messages: localeData } = getLocale(); - - const scrollToDetailedStatus = () => { - const history = createBrowserHistory(); - const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); - const location = history.location; - - if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) { - detailedStatuses[0].scrollIntoView(); - history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true }); - } - }; - - const getEmojiAnimationHandler = (swapTo) => { - return ({ target }) => { - target.src = target.getAttribute(swapTo); - }; - }; - - const locale = document.documentElement.lang; - - const dateTimeFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - - const dateFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'short', - day: 'numeric', - timeFormat: false, - }); - - const timeFormat = new Intl.DateTimeFormat(locale, { - timeStyle: 'short', - }); - - const formatMessage = ({ id, defaultMessage }, values) => { - const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); - return messageFormat.format(values); - }; - - document.querySelectorAll('.emojify').forEach((content) => { - content.innerHTML = emojify(content.innerHTML); - }); - - document.querySelectorAll('time.formatted').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - const formattedDate = dateTimeFormat.format(datetime); - - content.title = formattedDate; - content.textContent = formattedDate; - }); - - const isToday = date => { - const today = new Date(); - - return date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear(); - }; - const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); - - document.querySelectorAll('time.relative-formatted').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - - let formattedContent; - - if (isToday(datetime)) { - const formattedTime = timeFormat.format(datetime); - - formattedContent = todayFormat.format({ time: formattedTime }); - } else { - formattedContent = dateFormat.format(datetime); - } - - content.title = formattedContent; - content.textContent = formattedContent; - }); - - document.querySelectorAll('time.time-ago').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - const now = new Date(); - - const timeGiven = content.getAttribute('datetime').includes('T'); - content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); - content.textContent = timeAgoString({ - formatMessage, - formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), - }, datetime, now, now.getFullYear(), timeGiven); - }); - - const reactComponents = document.querySelectorAll('[data-component]'); - if (reactComponents.length > 0) { - import(/* webpackChunkName: "containers/media_container" */ 'flavours/glitch/containers/media_container') - .then(({ default: MediaContainer }) => { - reactComponents.forEach((component) => { - Array.from(component.children).forEach((child) => { - component.removeChild(child); - }); - }); - - const content = document.createElement('div'); - - const root = createRoot(content); - root.render(); - document.body.appendChild(content); - scrollToDetailedStatus(); - }) - .catch(error => { - console.error(error); - scrollToDetailedStatus(); - }); - } else { - scrollToDetailedStatus(); - } - - Rails.delegate(document, '#user_account_attributes_username', 'input', throttle(() => { - const username = document.getElementById('user_account_attributes_username'); - - if (username.value && username.value.length > 0) { - axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => { - username.setCustomValidity(formatMessage(messages.usernameTaken)); - }).catch(() => { - username.setCustomValidity(''); - }); - } else { - username.setCustomValidity(''); - } - }, 500, { leading: false, trailing: true })); - - Rails.delegate(document, '#user_password,#user_password_confirmation', 'input', () => { - const password = document.getElementById('user_password'); - const confirmation = document.getElementById('user_password_confirmation'); - if (!confirmation) return; - - if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); - } else { - confirmation.setCustomValidity(''); - } - }); - - Rails.delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); - Rails.delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); - - Rails.delegate(document, '.status__content__spoiler-link', 'click', function() { - const statusEl = this.parentNode.parentNode; - - if (statusEl.dataset.spoiler === 'expanded') { - statusEl.dataset.spoiler = 'folded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); - } else { - statusEl.dataset.spoiler = 'expanded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); - } - - return false; - }); - - document.querySelectorAll('.status__content__spoiler-link').forEach((spoilerLink) => { - const statusEl = spoilerLink.parentNode.parentNode; - const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); - spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); - }); - - const toggleSidebar = () => { - const sidebar = document.querySelector('.sidebar ul'); - const toggleButton = document.querySelector('.sidebar__toggle__icon'); - - if (sidebar.classList.contains('visible')) { - document.body.style.overflow = null; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - document.body.style.overflow = 'hidden'; - toggleButton.setAttribute('aria-expanded', 'true'); - } - - toggleButton.classList.toggle('active'); - sidebar.classList.toggle('visible'); - }; - - Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { - toggleSidebar(); - }); - - Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', e => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - toggleSidebar(); - } - }); - - // Empty the honeypot fields in JS in case something like an extension - // automatically filled them. - Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { - ['user_website', 'user_confirm_password', 'registration_user_website', 'registration_user_confirm_password'].forEach(id => { - const field = document.getElementById(id); - if (field) { - field.value = ''; - } - }); - }); -} - -loadPolyfills() - .then(loadLocale) - .then(main) - .then(loadKeyboardExtensions) - .catch(error => { - console.error(error); - }); diff --git a/app/javascript/flavours/glitch/packs/public.tsx b/app/javascript/flavours/glitch/packs/public.tsx new file mode 100644 index 0000000000..1ecc3fa42f --- /dev/null +++ b/app/javascript/flavours/glitch/packs/public.tsx @@ -0,0 +1,356 @@ +import { createRoot } from 'react-dom/client'; + +import 'packs/public-path'; + +import { IntlMessageFormat } from 'intl-messageformat'; +import type { MessageDescriptor, PrimitiveType } from 'react-intl'; +import { defineMessages } from 'react-intl'; + +import Rails from '@rails/ujs'; +import axios from 'axios'; +import { throttle } from 'lodash'; + +import { timeAgoString } from 'flavours/glitch/components/relative_timestamp'; +import emojify from 'flavours/glitch/features/emoji/emoji'; +import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; +import { loadLocale, getLocale } from 'flavours/glitch/locales'; +import { loadPolyfills } from 'flavours/glitch/polyfills'; +import ready from 'flavours/glitch/ready'; + +import 'cocoon-js-vanilla'; + +const messages = defineMessages({ + usernameTaken: { + id: 'username.taken', + defaultMessage: 'That username is taken. Try another', + }, + passwordExceedsLength: { + id: 'password_confirmation.exceeds_maxlength', + defaultMessage: 'Password confirmation exceeds the maximum password length', + }, + passwordDoesNotMatch: { + id: 'password_confirmation.mismatching', + defaultMessage: 'Password confirmation does not match', + }, +}); + +function loaded() { + const { messages: localeData } = getLocale(); + + const locale = document.documentElement.lang; + + const dateTimeFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }); + + const dateFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + + const timeFormat = new Intl.DateTimeFormat(locale, { + timeStyle: 'short', + }); + + const formatMessage = ( + { id, defaultMessage }: MessageDescriptor, + values?: Record, + ) => { + let message: string | undefined = undefined; + + if (id) message = localeData[id]; + + if (!message) message = defaultMessage as string; + + const messageFormat = new IntlMessageFormat(message, locale); + return messageFormat.format(values) as string; + }; + + document.querySelectorAll('.emojify').forEach((content) => { + content.innerHTML = emojify(content.innerHTML); + }); + + document + .querySelectorAll('time.formatted') + .forEach((content) => { + const datetime = new Date(content.dateTime); + const formattedDate = dateTimeFormat.format(datetime); + + content.title = formattedDate; + content.textContent = formattedDate; + }); + + const isToday = (date: Date) => { + const today = new Date(); + + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ); + }; + const todayFormat = new IntlMessageFormat( + localeData['relative_format.today'] || 'Today at {time}', + locale, + ); + + document + .querySelectorAll('time.relative-formatted') + .forEach((content) => { + const datetime = new Date(content.dateTime); + + let formattedContent: string; + + if (isToday(datetime)) { + const formattedTime = timeFormat.format(datetime); + + formattedContent = todayFormat.format({ + time: formattedTime, + }) as string; + } else { + formattedContent = dateFormat.format(datetime); + } + + content.title = formattedContent; + content.textContent = formattedContent; + }); + + document + .querySelectorAll('time.time-ago') + .forEach((content) => { + const datetime = new Date(content.dateTime); + const now = new Date(); + + const timeGiven = content.dateTime.includes('T'); + content.title = timeGiven + ? dateTimeFormat.format(datetime) + : dateFormat.format(datetime); + content.textContent = timeAgoString( + { + formatMessage, + formatDate: (date: Date, options) => + new Intl.DateTimeFormat(locale, options).format(date), + }, + datetime, + now.getTime(), + now.getFullYear(), + timeGiven, + ); + }); + + const reactComponents = document.querySelectorAll('[data-component]'); + + if (reactComponents.length > 0) { + import( + /* webpackChunkName: "containers/media_container" */ 'flavours/glitch/containers/media_container' + ) + .then(({ default: MediaContainer }) => { + reactComponents.forEach((component) => { + Array.from(component.children).forEach((child) => { + component.removeChild(child); + }); + }); + + const content = document.createElement('div'); + + const root = createRoot(content); + root.render( + , + ); + document.body.appendChild(content); + + return true; + }) + .catch((error) => { + console.error(error); + }); + } + + Rails.delegate( + document, + 'input#user_account_attributes_username', + 'input', + throttle( + ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + if (target.value && target.value.length > 0) { + axios + .get('/api/v1/accounts/lookup', { params: { acct: target.value } }) + .then(() => { + target.setCustomValidity(formatMessage(messages.usernameTaken)); + return true; + }) + .catch(() => { + target.setCustomValidity(''); + }); + } else { + target.setCustomValidity(''); + } + }, + 500, + { leading: false, trailing: true }, + ), + ); + + Rails.delegate( + document, + '#user_password,#user_password_confirmation', + 'input', + () => { + const password = document.querySelector( + 'input#user_password', + ); + const confirmation = document.querySelector( + 'input#user_password_confirmation', + ); + if (!confirmation || !password) return; + + if ( + confirmation.value && + confirmation.value.length > password.maxLength + ) { + confirmation.setCustomValidity( + formatMessage(messages.passwordExceedsLength), + ); + } else if (password.value && password.value !== confirmation.value) { + confirmation.setCustomValidity( + formatMessage(messages.passwordDoesNotMatch), + ); + } else { + confirmation.setCustomValidity(''); + } + }, + ); + + Rails.delegate( + document, + 'button.status__content__spoiler-link', + 'click', + function () { + if (!(this instanceof HTMLButtonElement)) return; + + const statusEl = this.parentNode?.parentNode; + + if ( + !( + statusEl instanceof HTMLDivElement && + statusEl.classList.contains('.status__content') + ) + ) + return; + + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; + this.textContent = new IntlMessageFormat( + localeData['status.show_more'] || 'Show more', + locale, + ).format() as string; + } else { + statusEl.dataset.spoiler = 'expanded'; + this.textContent = new IntlMessageFormat( + localeData['status.show_less'] || 'Show less', + locale, + ).format() as string; + } + }, + ); + + document + .querySelectorAll('button.status__content__spoiler-link') + .forEach((spoilerLink) => { + const statusEl = spoilerLink.parentNode?.parentNode; + + if ( + !( + statusEl instanceof HTMLDivElement && + statusEl.classList.contains('.status__content') + ) + ) + return; + + const message = + statusEl.dataset.spoiler === 'expanded' + ? localeData['status.show_less'] || 'Show less' + : localeData['status.show_more'] || 'Show more'; + spoilerLink.textContent = new IntlMessageFormat( + message, + locale, + ).format() as string; + }); +} + +const toggleSidebar = () => { + const sidebar = document.querySelector('.sidebar ul'); + const toggleButton = document.querySelector( + 'a.sidebar__toggle__icon', + ); + + if (!sidebar || !toggleButton) return; + + if (sidebar.classList.contains('visible')) { + document.body.style.overflow = ''; + toggleButton.setAttribute('aria-expanded', 'false'); + } else { + document.body.style.overflow = 'hidden'; + toggleButton.setAttribute('aria-expanded', 'true'); + } + + toggleButton.classList.toggle('active'); + sidebar.classList.toggle('visible'); +}; + +Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { + toggleSidebar(); +}); + +Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + toggleSidebar(); + } +}); + +Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => { + if (target instanceof HTMLImageElement && target.dataset.original) + target.src = target.dataset.original; +}); +Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => { + if (target instanceof HTMLImageElement && target.dataset.static) + target.src = target.dataset.static; +}); + +// Empty the honeypot fields in JS in case something like an extension +// automatically filled them. +Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { + [ + 'user_website', + 'user_confirm_password', + 'registration_user_website', + 'registration_user_confirm_password', + ].forEach((id) => { + const field = document.querySelector(`input#${id}`); + if (field) { + field.value = ''; + } + }); +}); + +function main() { + ready(loaded).catch((error) => { + console.error(error); + }); +} + +loadPolyfills() + .then(loadLocale) + .then(main) + .then(loadKeyboardExtensions) + .catch((error) => { + console.error(error); + }); diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js deleted file mode 100644 index d9f3b68602..0000000000 --- a/app/javascript/flavours/glitch/packs/settings.js +++ /dev/null @@ -1,42 +0,0 @@ -import 'packs/public-path'; -import Rails from '@rails/ujs'; - -import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; -import { loadPolyfills } from 'flavours/glitch/polyfills'; -import 'cocoon-js-vanilla'; - -function main() { - const toggleSidebar = () => { - const sidebar = document.querySelector('.sidebar ul'); - const toggleButton = document.querySelector('.sidebar__toggle__icon'); - - if (sidebar.classList.contains('visible')) { - document.body.style.overflow = null; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - document.body.style.overflow = 'hidden'; - toggleButton.setAttribute('aria-expanded', 'true'); - } - - toggleButton.classList.toggle('active'); - sidebar.classList.toggle('visible'); - }; - - Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { - toggleSidebar(); - }); - - Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', e => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - toggleSidebar(); - } - }); -} - -loadPolyfills() - .then(main) - .then(loadKeyboardExtensions) - .catch(error => { - console.error(error); - }); diff --git a/app/javascript/flavours/glitch/reducers/blocks.js b/app/javascript/flavours/glitch/reducers/blocks.js deleted file mode 100644 index 1b65071634..0000000000 --- a/app/javascript/flavours/glitch/reducers/blocks.js +++ /dev/null @@ -1,22 +0,0 @@ -import Immutable from 'immutable'; - -import { - BLOCKS_INIT_MODAL, -} from '../actions/blocks'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - account_id: null, - }), -}); - -export default function mutes(state = initialState, action) { - switch (action.type) { - case BLOCKS_INIT_MODAL: - return state.withMutations((state) => { - state.setIn(['new', 'account_id'], action.account.get('id')); - }); - default: - return state; - } -} diff --git a/app/javascript/flavours/glitch/reducers/index.ts b/app/javascript/flavours/glitch/reducers/index.ts index 1049a39900..c67349e51f 100644 --- a/app/javascript/flavours/glitch/reducers/index.ts +++ b/app/javascript/flavours/glitch/reducers/index.ts @@ -7,7 +7,6 @@ import { accountsReducer } from './accounts'; import accounts_map from './accounts_map'; import alerts from './alerts'; import announcements from './announcements'; -import blocks from './blocks'; import boosts from './boosts'; import compose from './compose'; import contexts from './contexts'; @@ -27,7 +26,6 @@ import markers from './markers'; import media_attachments from './media_attachments'; import meta from './meta'; import { modalReducer } from './modal'; -import mutes from './mutes'; import { notificationPolicyReducer } from './notification_policy'; import { notificationRequestsReducer } from './notification_requests'; import notifications from './notifications'; @@ -65,8 +63,6 @@ const reducers = { settings, local_settings, push_notifications, - mutes, - blocks, boosts, server, contexts, diff --git a/app/javascript/flavours/glitch/reducers/mutes.js b/app/javascript/flavours/glitch/reducers/mutes.js deleted file mode 100644 index a9eb61ff83..0000000000 --- a/app/javascript/flavours/glitch/reducers/mutes.js +++ /dev/null @@ -1,31 +0,0 @@ -import Immutable from 'immutable'; - -import { - MUTES_INIT_MODAL, - MUTES_TOGGLE_HIDE_NOTIFICATIONS, - MUTES_CHANGE_DURATION, -} from '../actions/mutes'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - account: null, - notifications: true, - duration: 0, - }), -}); - -export default function mutes(state = initialState, action) { - switch (action.type) { - case MUTES_INIT_MODAL: - return state.withMutations((state) => { - state.setIn(['new', 'account'], action.account); - state.setIn(['new', 'notifications'], true); - }); - case MUTES_TOGGLE_HIDE_NOTIFICATIONS: - return state.updateIn(['new', 'notifications'], (old) => !old); - case MUTES_CHANGE_DURATION: - return state.setIn(['new', 'duration'], Number(action.duration)); - default: - return state; - } -} diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 13cc19a22d..b611d8bea0 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -106,17 +106,17 @@ } &.button-secondary { - color: $ui-button-secondary-color; + color: $highlight-text-color; background: transparent; padding: 6px 17px; - border: 1px solid $ui-button-secondary-border-color; + border: 1px solid $highlight-text-color; &:active, &:focus, &:hover { - border-color: $ui-button-secondary-focus-background-color; - color: $ui-button-secondary-focus-color; - background-color: $ui-button-secondary-focus-background-color; + border-color: lighten($highlight-text-color, 4%); + color: lighten($highlight-text-color, 4%); + background-color: transparent; text-decoration: none; } @@ -2017,6 +2017,118 @@ body > [data-popper-placement] { } } + &__domain-pill { + display: inline-flex; + background: rgba($highlight-text-color, 0.2); + border-radius: 4px; + border: 0; + color: $highlight-text-color; + font-weight: 500; + font-size: 12px; + line-height: 16px; + padding: 4px 8px; + + &.active { + color: $white; + background: $ui-highlight-color; + } + + &__popout { + background: var(--dropdown-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--dropdown-border-color); + box-shadow: var(--dropdown-shadow); + max-width: 320px; + padding: 16px; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 24px; + font-size: 14px; + line-height: 20px; + color: $darker-text-color; + + .link-button { + display: inline; + font-size: inherit; + line-height: inherit; + } + + &__header { + display: flex; + align-items: center; + gap: 12px; + + &__icon { + width: 40px; + height: 40px; + background: $ui-highlight-color; + color: $white; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + flex-shrink: 0; + } + + h3 { + font-size: 17px; + line-height: 22px; + color: $primary-text-color; + } + } + + &__handle { + border: 2px dashed $highlight-text-color; + background: rgba($highlight-text-color, 0.1); + padding: 12px 8px; + color: $highlight-text-color; + border-radius: 4px; + + &__label { + font-size: 11px; + line-height: 16px; + font-weight: 500; + } + + &__handle { + user-select: all; + } + } + + &__parts { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 12px; + line-height: 16px; + + & > div { + display: flex; + align-items: flex-start; + gap: 12px; + } + + &__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: $highlight-text-color; + } + + h6 { + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: $primary-text-color; + } + } + } + } + &__note { font-size: 14px; font-weight: 400; @@ -5883,6 +5995,10 @@ a.status-card { pointer-events: auto; user-select: text; display: flex; + + @media screen and (max-width: $no-gap-breakpoint) { + margin-top: auto; + } } .video-modal .video-player { @@ -6214,6 +6330,154 @@ a.status-card { margin-inline-start: 10px; } +.safety-action-modal { + width: 600px; + flex-direction: column; + + &__top, + &__bottom { + display: flex; + gap: 8px; + padding: 24px; + flex-direction: column; + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + } + + &__top { + border-radius: 16px 16px 0 0; + border-bottom: 0; + gap: 16px; + } + + &__bottom { + border-radius: 0 0 16px 16px; + border-top: 0; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + border-bottom: 0; + padding-bottom: 32px; + } + } + + &__header { + display: flex; + gap: 16px; + align-items: center; + font-size: 14px; + line-height: 20px; + color: $darker-text-color; + + &__icon { + border-radius: 64px; + background: $ui-highlight-color; + color: $white; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + + .icon { + width: 24px; + height: 24px; + } + } + + h1 { + font-size: 22px; + line-height: 28px; + color: $primary-text-color; + } + } + + &__bullet-points { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 16px; + line-height: 24px; + + & > div { + display: flex; + gap: 16px; + align-items: center; + } + + &__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + .icon { + width: 24px; + height: 24px; + } + } + } + + &__field-group { + display: flex; + flex-direction: column; + + label { + display: flex; + gap: 16px; + align-items: center; + font-size: 16px; + line-height: 24px; + height: 32px; + padding: 0 12px; + } + } + + &__caveats { + font-size: 14px; + padding: 0 12px; + + strong { + font-weight: 500; + } + } + + &__bottom { + padding-top: 0; + + &__collapsible { + display: none; + flex-direction: column; + gap: 16px; + } + + &.active { + background: var(--modal-background-variant-color); + padding-top: 24px; + + .safety-action-modal__bottom__collapsible { + display: flex; + } + } + } + + &__actions { + display: flex; + align-items: center; + gap: 8px; + justify-content: flex-end; + + .link-button { + padding: 10px 12px; + font-weight: 600; + } + } +} + .doodle-modal, .boost-modal, .confirmation-modal, @@ -7599,7 +7863,8 @@ img.modal-warning { display: flex; } -.radio-button { +.radio-button, +.check-box { font-size: 14px; position: relative; display: inline-flex; @@ -7618,17 +7883,19 @@ img.modal-warning { } &__input { - display: block; + display: flex; + align-items: center; + justify-content: center; position: relative; border: 2px solid $secondary-text-color; box-sizing: border-box; - width: 18px; - height: 18px; + width: 20px; + height: 20px; flex: 0 0 auto; border-radius: 50%; &.checked { - border-color: $secondary-text-color; + border-color: $ui-highlight-color; &::before { position: absolute; @@ -7637,9 +7904,31 @@ img.modal-warning { content: ''; display: block; border-radius: 50%; - width: 10px; - height: 10px; - background: $secondary-text-color; + width: 12px; + height: 12px; + background: $ui-highlight-color; + } + } + + .icon { + width: 18px; + height: 18px; + } + } +} + +.check-box { + &__input { + width: 18px; + height: 18px; + border-radius: 2px; + + &.checked { + background: $ui-highlight-color; + color: $white; + + &::before { + display: none; } } } @@ -8143,14 +8432,17 @@ noscript { font-size: 17px; line-height: 22px; color: $primary-text-color; - font-weight: 700; + font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; small { - display: block; - font-size: 15px; + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + line-height: 20px; color: $darker-text-color; font-weight: 400; overflow: hidden; @@ -8161,10 +8453,8 @@ noscript { } .icon-lock { - height: 16px; - width: 16px; - position: relative; - top: 3px; + height: 18px; + width: 18px; } } } @@ -9126,22 +9416,36 @@ noscript { } } +.safety-action-modal, .interaction-modal { max-width: 90vw; width: 600px; - background: var(--modal-background-color); - border: 1px solid var(--modal-border-color); - border-radius: 8px; +} + +.interaction-modal { overflow: visible; position: relative; display: block; - padding: 40px; + border-radius: 16px; + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + padding: 24px; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 16px 16px 0 0; + border-bottom: 0; + padding-bottom: 32px; + } h3 { font-size: 22px; line-height: 33px; font-weight: 700; - text-align: center; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; } p { @@ -9162,7 +9466,9 @@ noscript { &__icon { color: $highlight-text-color; - margin: 0 5px; + display: flex; + align-items: center; + justify-content: center; } &__lead { @@ -9195,6 +9501,7 @@ noscript { border: 0; padding: 15px - 4px 15px - 6px; flex: 1 1 auto; + min-width: 0; &::placeholder { color: lighten($darker-text-color, 4%); diff --git a/app/javascript/flavours/glitch/styles/variables.scss b/app/javascript/flavours/glitch/styles/variables.scss index 70bd86d33c..54dc54ac02 100644 --- a/app/javascript/flavours/glitch/styles/variables.scss +++ b/app/javascript/flavours/glitch/styles/variables.scss @@ -46,10 +46,10 @@ $ui-button-focus-background-color: $blurple-600 !default; $ui-button-focus-outline-color: $blurple-400 !default; $ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default; -$ui-button-secondary-color: $grey-100 !default; -$ui-button-secondary-border-color: $grey-100 !default; -$ui-button-secondary-focus-background-color: $grey-600 !default; -$ui-button-secondary-focus-color: $white !default; +$ui-button-secondary-color: $blurple-500 !default; +$ui-button-secondary-border-color: $blurple-500 !default; +$ui-button-secondary-focus-border-color: $blurple-300 !default; +$ui-button-secondary-focus-color: $blurple-300 !default; $ui-button-tertiary-color: $blurple-300 !default; $ui-button-tertiary-border-color: $blurple-300 !default; @@ -104,7 +104,8 @@ $dismiss-overlay-width: 4rem; --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)}; --dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)}, 0 8px 10px -6px #{rgba($base-shadow-color, 0.25)}; - --modal-background-color: #{darken($ui-base-color, 4%)}; + --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)}; + --modal-background-variant-color: #{rgba($ui-base-color, 0.7)}; --modal-border-color: #{lighten($ui-base-color, 4%)}; --background-border-color: #{lighten($ui-base-color, 4%)}; --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index e6b748da77..1aa31df187 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -2,12 +2,12 @@ pack: admin: - packs/admin.tsx - - packs/public.jsx - auth: packs/public.jsx + - packs/public.tsx + auth: packs/public.tsx common: filename: packs/common.js stylesheet: true - embed: packs/public.jsx + embed: packs/public.tsx error: packs/error.js home: filename: packs/home.js @@ -17,8 +17,8 @@ pack: - flavours/glitch/async/notifications mailer: modal: - public: packs/public.jsx - settings: packs/settings.js + public: packs/public.tsx + settings: packs/public.tsx sign_up: packs/sign_up.js share: packs/share.jsx diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index ecfb00729b..7c7df295d3 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -2,12 +2,12 @@ pack: admin: - admin.tsx - - public.jsx - auth: public.jsx + - public.tsx + auth: public.tsx common: filename: common.js stylesheet: true - embed: public.jsx + embed: public.tsx error: error.js home: filename: application.js @@ -17,8 +17,8 @@ pack: - features/notifications mailer: modal: - public: public.jsx - settings: public.jsx + public: public.tsx + settings: public.tsx sign_up: sign_up.js share: share.jsx diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js index e293657ad3..54296d0905 100644 --- a/app/javascript/mastodon/actions/blocks.js +++ b/app/javascript/mastodon/actions/blocks.js @@ -12,8 +12,6 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; -export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL'; - export function fetchBlocks() { return (dispatch, getState) => { dispatch(fetchBlocksRequest()); @@ -90,11 +88,12 @@ export function expandBlocksFail(error) { export function initBlockModal(account) { return dispatch => { - dispatch({ - type: BLOCKS_INIT_MODAL, - account, - }); - - dispatch(openModal({ modalType: 'BLOCK' })); + dispatch(openModal({ + modalType: 'BLOCK', + modalProps: { + accountId: account.get('id'), + acct: account.get('acct'), + }, + })); }; } diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index 718002613f..55c0a6ce9d 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -1,6 +1,8 @@ import api, { getLinks } from '../api'; import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed"; +import { openModal } from './modal'; + export * from "./domain_blocks_typed"; @@ -150,3 +152,12 @@ export function expandDomainBlocksFail(error) { error, }; } + +export const initDomainBlockModal = account => dispatch => dispatch(openModal({ + modalType: 'DOMAIN_BLOCK', + modalProps: { + domain: account.get('acct').split('@')[1], + acct: account.get('acct'), + accountId: account.get('id'), + }, +})); diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js index fb041078b8..99c113f414 100644 --- a/app/javascript/mastodon/actions/mutes.js +++ b/app/javascript/mastodon/actions/mutes.js @@ -12,10 +12,6 @@ export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; -export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; -export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; -export const MUTES_CHANGE_DURATION = 'MUTES_CHANGE_DURATION'; - export function fetchMutes() { return (dispatch, getState) => { dispatch(fetchMutesRequest()); @@ -92,26 +88,12 @@ export function expandMutesFail(error) { export function initMuteModal(account) { return dispatch => { - dispatch({ - type: MUTES_INIT_MODAL, - account, - }); - - dispatch(openModal({ modalType: 'MUTE' })); - }; -} - -export function toggleHideNotifications() { - return dispatch => { - dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS }); - }; -} - -export function changeMuteDuration(duration) { - return dispatch => { - dispatch({ - type: MUTES_CHANGE_DURATION, - duration, - }); + dispatch(openModal({ + modalType: 'MUTE', + modalProps: { + accountId: account.get('id'), + acct: account.get('acct'), + }, + })); }; } diff --git a/app/javascript/mastodon/components/check_box.tsx b/app/javascript/mastodon/components/check_box.tsx new file mode 100644 index 0000000000..7da8ef0ac5 --- /dev/null +++ b/app/javascript/mastodon/components/check_box.tsx @@ -0,0 +1,39 @@ +import classNames from 'classnames'; + +import DoneIcon from '@/material-icons/400-24px/done.svg?react'; + +import { Icon } from './icon'; + +interface Props { + value: string; + checked: boolean; + name: string; + onChange: (event: React.ChangeEvent) => void; + label: React.ReactNode; +} + +export const CheckBox: React.FC = ({ + name, + value, + checked, + onChange, + label, +}) => { + return ( + + ); +}; diff --git a/app/javascript/mastodon/components/relative_timestamp.tsx b/app/javascript/mastodon/components/relative_timestamp.tsx index 12530c2b17..b9e1e4f8fd 100644 --- a/app/javascript/mastodon/components/relative_timestamp.tsx +++ b/app/javascript/mastodon/components/relative_timestamp.tsx @@ -102,7 +102,7 @@ const getUnitDelay = (units: string) => { }; export const timeAgoString = ( - intl: IntlShape, + intl: Pick, date: Date, now: number, year: number, diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index b111a65385..3c6d26e349 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -209,7 +209,7 @@ class StatusActionBar extends ImmutablePureComponent { const { status, onBlockDomain } = this.props; const account = status.get('account'); - onBlockDomain(account.get('acct').split('@')[1]); + onBlockDomain(account); }; handleUnblockDomain = () => { diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 7a7cd9880f..7bd91c8c9d 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -1,4 +1,4 @@ -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; @@ -15,7 +15,7 @@ import { directCompose, } from '../actions/compose'; import { - blockDomain, + initDomainBlockModal, unblockDomain, } from '../actions/domain_blocks'; import { @@ -253,15 +253,8 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ dispatch(toggleStatusCollapse(status.get('id'), isCollapsed)); }, - onBlockDomain (domain) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), - }, - })); + onBlockDomain (account) { + dispatch(initDomainBlockModal(account)); }, onUnblockDomain (domain) { diff --git a/app/javascript/mastodon/features/account/components/domain_pill.jsx b/app/javascript/mastodon/features/account/components/domain_pill.jsx new file mode 100644 index 0000000000..0dadb947f9 --- /dev/null +++ b/app/javascript/mastodon/features/account/components/domain_pill.jsx @@ -0,0 +1,86 @@ +import PropTypes from 'prop-types'; +import { useState, useRef, useCallback } 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 = ({ domain, username, isSelf }) => { + 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 ( + <> + + + + {({ props }) => ( +
+
+
+

+
+ +
+
{isSelf ? : }
+
@{username}@{domain}
+
+ +
+
+
+ +
+
+

{isSelf ? : }

+
+
+ +
+
+ +
+
+

{isSelf ? : }

+
+
+
+ +

{isSelf ? }} /> : }} />}

+ + {expanded && ( + <> +

+

+ + )} +
+ )} +
+ + ); +}; + +DomainPill.propTypes = { + username: PropTypes.string.isRequired, + domain: PropTypes.string.isRequired, + isSelf: PropTypes.bool, +}; diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index c6295d7c4a..ecb1108ded 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -25,13 +25,15 @@ import { IconButton } from 'mastodon/components/icon_button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ShortNumber } from 'mastodon/components/short_number'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; -import { autoPlayGif, me, domain } from 'mastodon/initial_state'; +import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import AccountNoteContainer from '../containers/account_note_container'; import FollowRequestNoteContainer from '../containers/follow_request_note_container'; +import { DomainPill } from './domain_pill'; + const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -78,7 +80,7 @@ const messages = defineMessages({ const titleFromAccount = account => { const displayName = account.get('display_name'); - const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct'); + const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${localDomain}` : account.get('acct'); const prefix = displayName.trim().length === 0 ? account.get('username') : displayName; return `${prefix} (@${acct})`; @@ -252,7 +254,7 @@ class Header extends ImmutablePureComponent { } render () { - const { account, hidden, intl, domain } = this.props; + const { account, hidden, intl } = this.props; const { signedIn, permissions } = this.context.identity; if (!account) { @@ -393,7 +395,8 @@ class Header extends ImmutablePureComponent { const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); const isLocal = account.get('acct').indexOf('@') === -1; - const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const username = account.get('acct').split('@')[0]; + const domain = isLocal ? localDomain : account.get('acct').split('@')[1]; const isIndexable = !account.get('noindex'); const badges = []; @@ -438,7 +441,9 @@ class Header extends ImmutablePureComponent {

- @{acct} {lockedIcon} + @{username}@{domain} + + {lockedIcon}

diff --git a/app/javascript/mastodon/features/account_timeline/components/header.jsx b/app/javascript/mastodon/features/account_timeline/components/header.jsx index 7de8d3771b..a2463b8764 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx @@ -72,11 +72,7 @@ class Header extends ImmutablePureComponent { }; handleBlockDomain = () => { - const domain = this.props.account.get('acct').split('@')[1]; - - if (!domain) return; - - this.props.onBlockDomain(domain); + this.props.onBlockDomain(this.props.account); }; handleUnblockDomain = () => { diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index 84a303c37a..071dbdbfb7 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -17,7 +17,7 @@ import { mentionCompose, directCompose, } from '../../../actions/compose'; -import { blockDomain, unblockDomain } from '../../../actions/domain_blocks'; +import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blocks'; import { openModal } from '../../../actions/modal'; import { initMuteModal } from '../../../actions/mutes'; import { initReport } from '../../../actions/reports'; @@ -140,15 +140,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onBlockDomain (domain) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), - }, - })); + onBlockDomain (account) { + dispatch(initDomainBlockModal(account)); }, onUnblockDomain (domain) { diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx index d84c85806e..a686a0a849 100644 --- a/app/javascript/mastodon/features/getting_started/index.jsx +++ b/app/javascript/mastodon/features/getting_started/index.jsx @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; @@ -19,7 +20,6 @@ import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import StarIcon from '@/material-icons/400-24px/star.svg?react'; -import TagIcon from '@/material-icons/400-24px/tag.svg?react'; import { fetchFollowRequests } from 'mastodon/actions/accounts'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; @@ -112,7 +112,7 @@ class GettingStarted extends ImmutablePureComponent { if (showTrends) { navItems.push( - , + , ); } diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index c243a49129..a4951260ab 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -159,7 +159,7 @@ class ActionBar extends PureComponent { const { status, onBlockDomain } = this.props; const account = status.get('account'); - onBlockDomain(account.get('acct').split('@')[1]); + onBlockDomain(account); }; handleUnblockDomain = () => { diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index a3034bb570..44db9d9c3f 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -34,7 +34,7 @@ import { directCompose, } from '../../actions/compose'; import { - blockDomain, + initDomainBlockModal, unblockDomain, } from '../../actions/domain_blocks'; import { @@ -463,15 +463,8 @@ class Status extends ImmutablePureComponent { this.props.dispatch(unblockAccount(account.get('id'))); }; - handleBlockDomainClick = domain => { - this.props.dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: {domain} }} />, - confirm: this.props.intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => this.props.dispatch(blockDomain(domain)), - }, - })); + handleBlockDomainClick = account => { + this.props.dispatch(initDomainBlockModal(account)); }; handleUnblockDomainClick = domain => { diff --git a/app/javascript/mastodon/features/ui/components/block_modal.jsx b/app/javascript/mastodon/features/ui/components/block_modal.jsx index cfac692324..fc9233a9cc 100644 --- a/app/javascript/mastodon/features/ui/components/block_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/block_modal.jsx @@ -1,100 +1,116 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useCallback, useState } from 'react'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; +import classNames from 'classnames'; -import { blockAccount } from '../../../actions/accounts'; -import { closeModal } from '../../../actions/modal'; -import { initReport } from '../../../actions/reports'; -import { Button } from '../../../components/button'; -import { makeGetAccount } from '../../../selectors'; +import { useDispatch } from 'react-redux'; -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import BlockIcon from '@/material-icons/400-24px/block.svg?react'; +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { blockAccount } from 'mastodon/actions/accounts'; +import { closeModal } from 'mastodon/actions/modal'; +import { Button } from 'mastodon/components/button'; +import { Icon } from 'mastodon/components/icon'; - const mapStateToProps = state => ({ - account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])), - }); +export const BlockModal = ({ accountId, acct }) => { + const dispatch = useDispatch(); + const [expanded, setExpanded] = useState(false); - return mapStateToProps; -}; + const domain = acct.split('@')[1]; -const mapDispatchToProps = dispatch => { - return { - onConfirm(account) { - dispatch(blockAccount(account.get('id'))); - }, + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockAccount(accountId)); + }, [dispatch, accountId]); - onBlockAndReport(account) { - dispatch(blockAccount(account.get('id'))); - dispatch(initReport(account)); - }, + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); - onClose() { - dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })); - }, - }; -}; + const handleToggleLearnMore = useCallback(() => { + setExpanded(!expanded); + }, [expanded, setExpanded]); -class BlockModal extends PureComponent { + return ( +
+
+
+
+ +
- static propTypes = { - account: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - onBlockAndReport: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleClick = () => { - this.props.onClose(); - this.props.onConfirm(this.props.account); - }; - - handleSecondary = () => { - this.props.onClose(); - this.props.onBlockAndReport(this.props.account); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - render () { - const { account } = this.props; - - return ( -
-
-

- @{account.get('acct')} }} - /> -

+
+

+
@{acct}
+
-
-
+ +
+ {domain && ( +
+
+ {domain} }} + /> +
+
+ )} + +
+ {domain && ( + + )} + +
+ + - - + +
- ); - } +
+ ); +}; -} +BlockModal.propTypes = { + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; -export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(BlockModal)); +export default BlockModal; diff --git a/app/javascript/mastodon/features/ui/components/domain_block_modal.jsx b/app/javascript/mastodon/features/ui/components/domain_block_modal.jsx new file mode 100644 index 0000000000..e69db63489 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/domain_block_modal.jsx @@ -0,0 +1,106 @@ +import PropTypes from 'prop-types'; +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { useDispatch } from 'react-redux'; + +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react'; +import HistoryIcon from '@/material-icons/400-24px/history.svg?react'; +import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { blockAccount } from 'mastodon/actions/accounts'; +import { blockDomain } from 'mastodon/actions/domain_blocks'; +import { closeModal } from 'mastodon/actions/modal'; +import { Button } from 'mastodon/components/button'; +import { Icon } from 'mastodon/components/icon'; + +export const DomainBlockModal = ({ domain, accountId, acct }) => { + const dispatch = useDispatch(); + + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockDomain(domain)); + }, [dispatch, domain]); + + const handleSecondaryClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(blockAccount(accountId)); + }, [dispatch, accountId]); + + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); + + return ( +
+
+
+
+ +
+ +
+

+
{domain}
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+ + +
+ + + + +
+
+
+ ); +}; + +DomainBlockModal.propTypes = { + domain: PropTypes.string.isRequired, + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; + +export default DomainBlockModal; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 46f0dc706f..97d7706da4 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -7,6 +7,7 @@ import Base from 'mastodon/components/modal_root'; import { MuteModal, BlockModal, + DomainBlockModal, ReportModal, EmbedModal, ListEditor, @@ -41,6 +42,7 @@ export const MODAL_COMPONENTS = { 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), 'MUTE': MuteModal, 'BLOCK': BlockModal, + 'DOMAIN_BLOCK': DomainBlockModal, 'REPORT': ReportModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'EMBED': EmbedModal, diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.jsx b/app/javascript/mastodon/features/ui/components/mute_modal.jsx index fa81ea81a3..e9dc1a7c2e 100644 --- a/app/javascript/mastodon/features/ui/components/mute_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/mute_modal.jsx @@ -1,138 +1,154 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useCallback, useState } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; +import classNames from 'classnames'; -import Toggle from 'react-toggle'; +import { useDispatch } from 'react-redux'; -import { muteAccount } from '../../../actions/accounts'; -import { closeModal } from '../../../actions/modal'; -import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes'; -import { Button } from '../../../components/button'; + +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; +import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; +import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; +import { muteAccount } from 'mastodon/actions/accounts'; +import { closeModal } from 'mastodon/actions/modal'; +import { Button } from 'mastodon/components/button'; +import { CheckBox } from 'mastodon/components/check_box'; +import { Icon } from 'mastodon/components/icon'; +import { RadioButton } from 'mastodon/components/radio_button'; const messages = defineMessages({ minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' }, hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' }, days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' }, - indefinite: { id: 'mute_modal.indefinite', defaultMessage: 'Indefinite' }, + indefinite: { id: 'mute_modal.indefinite', defaultMessage: 'Until I unmute them' }, + hideFromNotifications: { id: 'mute_modal.hide_from_notifications', defaultMessage: 'Hide from notifications' }, }); -const mapStateToProps = state => { - return { - account: state.getIn(['mutes', 'new', 'account']), - notifications: state.getIn(['mutes', 'new', 'notifications']), - muteDuration: state.getIn(['mutes', 'new', 'duration']), - }; +const RadioButtonLabel = ({ name, value, currentValue, onChange, label }) => ( + +); + +RadioButtonLabel.propTypes = { + name: PropTypes.string, + value: PropTypes.oneOf([PropTypes.string, PropTypes.number, PropTypes.bool]), + currentValue: PropTypes.oneOf([PropTypes.string, PropTypes.number, PropTypes.bool]), + checked: PropTypes.bool, + onChange: PropTypes.func, + label: PropTypes.node, }; -const mapDispatchToProps = dispatch => { - return { - onConfirm(account, notifications, muteDuration) { - dispatch(muteAccount(account.get('id'), notifications, muteDuration)); - }, +export const MuteModal = ({ accountId, acct }) => { + const intl = useIntl(); + const dispatch = useDispatch(); + const [notifications, setNotifications] = useState(true); + const [muteDuration, setMuteDuration] = useState('0'); + const [expanded, setExpanded] = useState(false); - onClose() { - dispatch(closeModal({ - modalType: undefined, - ignoreFocus: false, - })); - }, + const handleClick = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + dispatch(muteAccount(accountId, notifications, muteDuration)); + }, [dispatch, accountId, notifications, muteDuration]); - onToggleNotifications() { - dispatch(toggleHideNotifications()); - }, + const handleCancel = useCallback(() => { + dispatch(closeModal({ modalType: undefined, ignoreFocus: false })); + }, [dispatch]); - onChangeMuteDuration(e) { - dispatch(changeMuteDuration(e.target.value)); - }, - }; -}; + const handleToggleNotifications = useCallback(({ target }) => { + setNotifications(target.checked); + }, [setNotifications]); -class MuteModal extends PureComponent { + const handleChangeMuteDuration = useCallback(({ target }) => { + setMuteDuration(target.value); + }, [setMuteDuration]); - static propTypes = { - account: PropTypes.object.isRequired, - notifications: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - onToggleNotifications: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - muteDuration: PropTypes.number.isRequired, - onChangeMuteDuration: PropTypes.func.isRequired, - }; + const handleToggleSettings = useCallback(() => { + setExpanded(!expanded); + }, [expanded, setExpanded]); - handleClick = () => { - this.props.onClose(); - this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - toggleNotifications = () => { - this.props.onToggleNotifications(); - }; - - changeMuteDuration = (e) => { - this.props.onChangeMuteDuration(e); - }; - - render () { - const { account, notifications, muteDuration, intl } = this.props; - - return ( -
-
-

- @{account.get('acct')} }} - /> -

-

- -

-
- - + return ( +
+
+
+
+
-
- : - +
+

+
@{acct}
-
-
+ +
+
+
+ + + + +
+ +
+ +
+
+ +
+ + +
+ + - + +
- ); - } +
+ ); +}; -} +MuteModal.propTypes = { + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, +}; -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(MuteModal)); +export default MuteModal; diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index de9b6b4010..e1f5bfdaf6 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -118,6 +118,10 @@ export function BlockModal () { return import(/* webpackChunkName: "modals/block_modal" */'../components/block_modal'); } +export function DomainBlockModal () { + return import(/* webpackChunkName: "modals/domain_block_modal" */'../components/domain_block_modal'); +} + export function ReportModal () { return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); } diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index bf3a1a43a9..31642e604b 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -500,7 +500,6 @@ "status.delete": "Borrar", "status.detailed_status": "Vista de conversación detallada", "status.edit": "Editar", - "status.edited": "Editau {date}", "status.edited_x_times": "Editau {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Incrustado", "status.filter": "Filtrar esta publicación", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 38653b4a34..17071aae48 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -646,7 +646,6 @@ "status.direct": "إشارة خاصة لـ @{name}", "status.direct_indicator": "إشارة خاصة", "status.edit": "تعديل", - "status.edited": "عُدّل في {date}", "status.edited_x_times": "عُدّل {count, plural, zero {} one {مرةً واحدة} two {مرّتان} few {{count} مرات} many {{count} مرة} other {{count} مرة}}", "status.embed": "إدماج", "status.favourite": "فضّل", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index 76fa44202d..de5e310d5a 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -429,7 +429,6 @@ "status.delete": "Desaniciar", "status.direct": "Mentar a @{name} per privao", "status.direct_indicator": "Mención privada", - "status.edited": "Editóse'l {date}", "status.edited_x_times": "Editóse {count, plural, one {{count} vegada} other {{count} vegaes}}", "status.embed": "Empotrar", "status.filter": "Peñerar esti artículu", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 6e0c5afb60..21e2622ba9 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -662,10 +662,11 @@ "status.direct": "Згадаць асабіста @{name}", "status.direct_indicator": "Асабістае згадванне", "status.edit": "Рэдагаваць", - "status.edited": "Адрэдагавана {date}", + "status.edited": "Апошняе рэдагаванне {date}", "status.edited_x_times": "Рэдагавана {count, plural, one {{count} раз} few {{count} разы} many {{count} разоў} other {{count} разу}}", "status.embed": "Убудаваць", "status.favourite": "Упадабанае", + "status.favourites": "{count, plural, one {# упадабанае} few {# упадабаныя} many {# упадабаных} other {# упадабанага}}", "status.filter": "Фільтраваць гэты допіс", "status.filtered": "Адфільтравана", "status.hide": "Схаваць допіс", @@ -686,6 +687,7 @@ "status.reblog": "Пашырыць", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю", "status.reblogged_by": "{name} пашырыў(-ла)", + "status.reblogs": "{count, plural, one {# пашырэнне} few {# пашырэнні} many {# пашырэнняў} other {# пашырэння}}", "status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.", "status.redraft": "Выдаліць і паправіць", "status.remove_bookmark": "Выдаліць закладку", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index ab3eda9cd3..ab6468d8e8 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -648,7 +648,6 @@ "status.direct": "Частно споменаване на @{name}", "status.direct_indicator": "Частно споменаване", "status.edit": "Редактиране", - "status.edited": "Редактирано на {date}", "status.edited_x_times": "Редактирано {count, plural,one {{count} път} other {{count} пъти}}", "status.embed": "Вграждане", "status.favourite": "Любимо", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index 9e0979641e..77d42f3536 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -584,7 +584,7 @@ "status.direct": "Menegiñ @{name} ent-prevez", "status.direct_indicator": "Meneg prevez", "status.edit": "Kemmañ", - "status.edited": "Aozet {date}", + "status.edited": "Kemmet da ziwezhañ d'an {date}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.embed": "Enframmañ", "status.favourite": "Muiañ-karet", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 27c1532768..fde638411e 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -662,7 +662,6 @@ "status.direct": "Menciona privadament @{name}", "status.direct_indicator": "Menció privada", "status.edit": "Edita", - "status.edited": "Editat {date}", "status.edited_x_times": "Editat {count, plural, one {{count} vegada} other {{count} vegades}}", "status.embed": "Incrusta", "status.favourite": "Favorit", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index a78a45c8de..27526e3454 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -559,7 +559,6 @@ "status.direct": "بە شێوەیەکی تایبەت باسی @{name} بکە", "status.direct_indicator": "ئاماژەی تایبەت", "status.edit": "دەستکاری", - "status.edited": "بەشداری {date}", "status.edited_x_times": "دەستکاریکراوە {count, plural, one {{count} کات} other {{count} کات}}", "status.embed": "نیشتەجێ بکە", "status.filter": "ئەم پۆستە فلتەر بکە", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 5beb06d9b2..5a8493fa07 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -646,7 +646,6 @@ "status.direct": "Soukromě zmínit @{name}", "status.direct_indicator": "Soukromá zmínka", "status.edit": "Upravit", - "status.edited": "Upraveno {date}", "status.edited_x_times": "Upraveno {count, plural, one {{count}krát} few {{count}krát} many {{count}krát} other {{count}krát}}", "status.embed": "Vložit na web", "status.favourite": "Oblíbit", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index e55dff9254..0106df1877 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -661,7 +661,6 @@ "status.direct": "Crybwyll yn breifat @{name}", "status.direct_indicator": "Crybwyll preifat", "status.edit": "Golygu", - "status.edited": "Golygwyd {date}", "status.edited_x_times": "Golygwyd {count, plural, one {count} two {count} other {{count} gwaith}}", "status.embed": "Mewnblannu", "status.favourite": "Hoffi", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 4f03c55bd4..df17791f1d 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -662,10 +662,11 @@ "status.direct": "Privat omtale @{name}", "status.direct_indicator": "Privat omtale", "status.edit": "Redigér", - "status.edited": "Redigeret {date}", + "status.edited": "Senest redigeret {date}", "status.edited_x_times": "Redigeret {count, plural, one {{count} gang} other {{count} gange}}", "status.embed": "Indlejr", "status.favourite": "Favorit", + "status.favourites": "{count, plural, one {# favorit} other {# favoritter}}", "status.filter": "Filtrér dette indlæg", "status.filtered": "Filtreret", "status.hide": "Skjul indlæg", @@ -686,6 +687,7 @@ "status.reblog": "Fremhæv", "status.reblog_private": "Boost med oprindelig synlighed", "status.reblogged_by": "{name} fremhævede", + "status.reblogs": "{count, plural, one {# boost} other {# boosts}}", "status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.", "status.redraft": "Slet og omformulér", "status.remove_bookmark": "Fjern bogmærke", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 70537cdb05..17e1ca1dd0 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -662,10 +662,11 @@ "status.direct": "@{name} privat erwähnen", "status.direct_indicator": "Private Erwähnung", "status.edit": "Beitrag bearbeiten", - "status.edited": "Bearbeitet {date}", + "status.edited": "Zuletzt am {date} bearbeitet", "status.edited_x_times": "{count, plural, one {{count}-mal} other {{count}-mal}} bearbeitet", "status.embed": "Beitrag per iFrame einbetten", "status.favourite": "Favorisieren", + "status.favourites": "{count, plural, one {Favorit} other {Favoriten}}", "status.filter": "Beitrag filtern", "status.filtered": "Gefiltert", "status.hide": "Beitrag ausblenden", @@ -686,6 +687,7 @@ "status.reblog": "Teilen", "status.reblog_private": "Mit der ursprünglichen Zielgruppe teilen", "status.reblogged_by": "{name} teilte", + "status.reblogs": "{count, plural, one {geteilt} other {geteilt}}", "status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.", "status.redraft": "Löschen und neu erstellen", "status.remove_bookmark": "Lesezeichen entfernen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 5d1f03b090..c8d90bf0c5 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -586,7 +586,6 @@ "status.direct": "Ιδιωτική επισήμανση @{name}", "status.direct_indicator": "Ιδιωτική επισήμανση", "status.edit": "Επεξεργασία", - "status.edited": "Επεξεργάστηκε στις {date}", "status.edited_x_times": "Επεξεργάστηκε {count, plural, one {{count} φορά} other {{count} φορές}}", "status.embed": "Ενσωμάτωσε", "status.favourite": "Αγαπημένα", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index e88209625f..c9b280f84e 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -640,7 +640,6 @@ "status.direct": "Privately mention @{name}", "status.direct_indicator": "Private mention", "status.edit": "Edit", - "status.edited": "Edited {date}", "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}", "status.embed": "Embed", "status.favourite": "Favourite", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 8a66695f32..376dfb7e4b 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -89,6 +89,14 @@ "announcement.announcement": "Announcement", "attachments_list.unprocessed": "(unprocessed)", "audio.hide": "Hide audio", + "block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.", + "block_modal.show_less": "Show less", + "block_modal.show_more": "Show more", + "block_modal.they_cant_mention": "They can't mention or follow you.", + "block_modal.they_cant_see_posts": "They can't see your posts and you won't see theirs.", + "block_modal.they_will_know": "They can see that they're blocked.", + "block_modal.title": "Block user?", + "block_modal.you_wont_see_mentions": "You won't see posts that mention them.", "boost_modal.combo": "You can press {combo} to skip this next time", "bundle_column_error.copy_stacktrace": "Copy error report", "bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.", @@ -160,9 +168,7 @@ "compose_form.spoiler.unmarked": "Add content warning", "compose_form.spoiler_placeholder": "Content warning (optional)", "confirmation_modal.cancel": "Cancel", - "confirmations.block.block_and_report": "Block & Report", "confirmations.block.confirm": "Block", - "confirmations.block.message": "Are you sure you want to block {name}?", "confirmations.cancel_follow_request.confirm": "Withdraw request", "confirmations.cancel_follow_request.message": "Are you sure you want to withdraw your request to follow {name}?", "confirmations.delete.confirm": "Delete", @@ -171,15 +177,13 @@ "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.discard_edit_media.confirm": "Discard", "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?", - "confirmations.domain_block.confirm": "Block entire domain", + "confirmations.domain_block.confirm": "Block server", "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.", "confirmations.edit.confirm": "Edit", "confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?", "confirmations.logout.confirm": "Log out", "confirmations.logout.message": "Are you sure you want to log out?", "confirmations.mute.confirm": "Mute", - "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.", - "confirmations.mute.message": "Are you sure you want to mute {name}?", "confirmations.redraft.confirm": "Delete & redraft", "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.", "confirmations.reply.confirm": "Reply", @@ -205,6 +209,27 @@ "dismissable_banner.explore_statuses": "These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.", "dismissable_banner.explore_tags": "These are hashtags that are gaining traction on the social web today. Hashtags that are used by more different people are ranked higher.", "dismissable_banner.public_timeline": "These are the most recent public posts from people on the social web that people on {domain} follow.", + "domain_block_modal.block": "Block server", + "domain_block_modal.block_account_instead": "Block @{name} instead", + "domain_block_modal.they_can_interact_with_old_posts": "People from this server can interact with your old posts.", + "domain_block_modal.they_cant_follow": "Nobody from this server can follow you.", + "domain_block_modal.they_wont_know": "They won't know they've been blocked.", + "domain_block_modal.title": "Block domain?", + "domain_block_modal.you_will_lose_followers": "All your followers from this server will be removed.", + "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 .", + "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 .", + "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.", "embed.instructions": "Embed this post on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", @@ -402,9 +427,15 @@ "loading_indicator.label": "Loading…", "media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}", "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.", - "mute_modal.duration": "Duration", - "mute_modal.hide_notifications": "Hide notifications from this user?", - "mute_modal.indefinite": "Indefinite", + "mute_modal.hide_from_notifications": "Hide from notifications", + "mute_modal.hide_options": "Hide options", + "mute_modal.indefinite": "Until I unmute them", + "mute_modal.show_options": "Show options", + "mute_modal.they_can_mention_and_follow": "They can mention and follow you, but you won't see them.", + "mute_modal.they_wont_know": "They won't know they've been muted.", + "mute_modal.title": "Mute user?", + "mute_modal.you_wont_see_mentions": "You won't see posts that mention them.", + "mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.", "navigation_bar.about": "About", "navigation_bar.advanced_interface": "Open in advanced web interface", "navigation_bar.blocks": "Blocked users", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 5d2dc61135..1fb4e2bd11 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -617,7 +617,6 @@ "status.direct": "Private mencii @{name}", "status.direct_indicator": "Privata mencio", "status.edit": "Redakti", - "status.edited": "Redaktita {date}", "status.edited_x_times": "Redactita {count, plural, one {{count} fojon} other {{count} fojojn}}", "status.embed": "Enkorpigi", "status.favourite": "Ŝatata", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 35802420d0..a573419f40 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -662,10 +662,11 @@ "status.direct": "Mención privada a @{name}", "status.direct_indicator": "Mención privada", "status.edit": "Editar", - "status.edited": "Editado {date}", + "status.edited": "Última edición: {date}", "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Insertar", "status.favourite": "Marcar como favorito", + "status.favourites": "{count, plural, one {# voto} other {# votos}}", "status.filter": "Filtrar este mensaje", "status.filtered": "Filtrado", "status.hide": "Ocultar mensaje", @@ -686,6 +687,7 @@ "status.reblog": "Adherir", "status.reblog_private": "Adherir a la audiencia original", "status.reblogged_by": "{name} adhirió", + "status.reblogs": "{count, plural, one {adhesión} other {adhesiones}}", "status.reblogs.empty": "Todavía nadie adhirió a este mensaje. Cuando alguien lo haga, se mostrará acá.", "status.redraft": "Eliminar mensaje original y editarlo", "status.remove_bookmark": "Quitar marcador", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index b1a7757081..7df5a384a8 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -662,10 +662,11 @@ "status.direct": "Mención privada @{name}", "status.direct_indicator": "Mención privada", "status.edit": "Editar", - "status.edited": "Editado {date}", + "status.edited": "Última edición {date}", "status.edited_x_times": "Editado {count, plural, one {{count} time} other {{count} veces}}", "status.embed": "Incrustado", "status.favourite": "Favorito", + "status.favourites": "{count, plural, one {favorito} other {favoritos}}", "status.filter": "Filtrar esta publicación", "status.filtered": "Filtrado", "status.hide": "Ocultar toot", @@ -686,6 +687,7 @@ "status.reblog": "Retootear", "status.reblog_private": "Implusar a la audiencia original", "status.reblogged_by": "Retooteado por {name}", + "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", "status.reblogs.empty": "Nadie retooteó este toot todavía. Cuando alguien lo haga, aparecerá aquí.", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index d7f54a3e2a..5ba7179cba 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -238,11 +238,11 @@ "empty_column.followed_tags": "No has seguido ninguna etiqueta todavía. Cuando lo hagas, se mostrarán aquí.", "empty_column.hashtag": "No hay nada en este hashtag aún.", "empty_column.home": "¡Tu línea temporal está vacía! Sigue a más personas para rellenarla.", - "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.", - "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.", + "empty_column.list": "Aún no hay nada en esta lista. Cuando los miembros de esta lista publiquen nuevos estados, estos aparecerán aquí.", + "empty_column.lists": "No tienes ninguna lista. Cuando crees una, se mostrará aquí.", "empty_column.mutes": "Aún no has silenciado a ningún usuario.", "empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.", - "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.", + "empty_column.notifications": "Aún no tienes ninguna notificación. Cuando otras personas interactúen contigo, aparecerán aquí.", "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo", "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.", "error.unexpected_crash.explanation_addons": "No se pudo mostrar correctamente esta página. Este error probablemente fue causado por un complemento del navegador web o por herramientas de traducción automática.", @@ -258,7 +258,7 @@ "explore.trending_tags": "Etiquetas", "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que ha accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.", "filter_modal.added.context_mismatch_title": "¡El contexto no coincide!", - "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, necesitará cambiar la fecha de caducidad para que se aplique.", + "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, tendrás que cambiar la fecha de caducidad para que se aplique.", "filter_modal.added.expired_title": "¡Filtro caducado!", "filter_modal.added.review_and_configure": "Para revisar y configurar esta categoría de filtros, vaya a {settings_link}.", "filter_modal.added.review_and_configure_title": "Ajustes de filtro", @@ -662,10 +662,11 @@ "status.direct": "Mención privada @{name}", "status.direct_indicator": "Mención privada", "status.edit": "Editar", - "status.edited": "Editado {date}", + "status.edited": "Última edición {date}", "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Incrustado", "status.favourite": "Favorito", + "status.favourites": "{count, plural, one {favorito} other {favoritos}}", "status.filter": "Filtrar esta publicación", "status.filtered": "Filtrado", "status.hide": "Ocultar publicación", @@ -686,6 +687,7 @@ "status.reblog": "Impulsar", "status.reblog_private": "Impulsar a la audiencia original", "status.reblogged_by": "Impulsado por {name}", + "status.reblogs": "{count, plural, one {impulso} other {impulsos}}", "status.reblogs.empty": "Nadie ha impulsado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.", "status.redraft": "Borrar y volver a borrador", "status.remove_bookmark": "Eliminar marcador", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 79e376693e..69c59f101a 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -647,7 +647,6 @@ "status.direct": "Maini privaatselt @{name}", "status.direct_indicator": "Privaatne mainimine", "status.edit": "Muuda", - "status.edited": "{date} muudetud", "status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}", "status.embed": "Manustamine", "status.favourite": "Lemmik", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 4af5aaaaf5..90b691ceb6 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -662,10 +662,11 @@ "status.direct": "Aipatu pribatuki @{name}", "status.direct_indicator": "Aipamen pribatua", "status.edit": "Editatu", - "status.edited": "Editatua {date}", + "status.edited": "Azken edizioa: {date}", "status.edited_x_times": "{count, plural, one {behin} other {{count} aldiz}} editatua", "status.embed": "Txertatu", "status.favourite": "Gogokoa", + "status.favourites": "{count, plural, one {gogoko} other {gogoko}}", "status.filter": "Iragazi bidalketa hau", "status.filtered": "Iragazita", "status.hide": "Tuta ezkutatu", @@ -686,6 +687,7 @@ "status.reblog": "Bultzada", "status.reblog_private": "Bultzada jatorrizko hartzaileei", "status.reblogged_by": "{name}(r)en bultzada", + "status.reblogs": "{count, plural, one {bultzada} other {bultzada}}", "status.reblogs.empty": "Inork ez dio bultzada eman bidalketa honi oraindik. Inork egiten badu, hemen agertuko da.", "status.redraft": "Ezabatu eta berridatzi", "status.remove_bookmark": "Kendu laster-marka", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 2ca44a41aa..aaa01a5dc7 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -646,7 +646,6 @@ "status.direct": "اشارهٔ خصوصی به ‪@{name}‬", "status.direct_indicator": "اشارهٔ خصوصی", "status.edit": "ویرایش", - "status.edited": "ویرایش شده در {date}", "status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد", "status.embed": "جاسازی", "status.favourite": "برگزیده‌", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 9acae62de4..703992a0e7 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -483,7 +483,7 @@ "notifications.policy.filter_not_followers_title": "Henkilöt, jotka eivät seuraa sinua", "notifications.policy.filter_not_following_hint": "Kunnes hyväksyt ne manuaalisesti", "notifications.policy.filter_not_following_title": "Henkilöt, joita et seuraa", - "notifications.policy.filter_private_mentions_hint": "Suodatetaan, ellei se vastaa omaan mainintaan tai jos seuraat lähettäjää", + "notifications.policy.filter_private_mentions_hint": "Suodatetaan, ellei se vastaa omaan mainintaasi tai ellet seuraa lähettäjää", "notifications.policy.filter_private_mentions_title": "Ei-toivotut yksityismaininnat", "notifications.policy.title": "Suodata ilmoitukset pois kohteesta…", "notifications_permission_banner.enable": "Ota työpöytäilmoitukset käyttöön", @@ -662,10 +662,11 @@ "status.direct": "Mainitse @{name} yksityisesti", "status.direct_indicator": "Yksityinen maininta", "status.edit": "Muokkaa", - "status.edited": "Muokattu {date}", + "status.edited": "Viimeksi muokattu {date}", "status.edited_x_times": "Muokattu {count, plural, one {{count} kerran} other {{count} kertaa}}", "status.embed": "Upota", "status.favourite": "Suosikki", + "status.favourites": "{count, plural, one {suosikki} other {suosikkia}}", "status.filter": "Suodata tämä julkaisu", "status.filtered": "Suodatettu", "status.hide": "Piilota julkaisu", @@ -686,6 +687,7 @@ "status.reblog": "Tehosta", "status.reblog_private": "Tehosta alkuperäiselle yleisölle", "status.reblogged_by": "{name} tehosti", + "status.reblogs": "{count, plural, one {tehostus} other {tehostusta}}", "status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.", "status.redraft": "Poista ja palauta muokattavaksi", "status.remove_bookmark": "Poista kirjanmerkki", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index 14bf1af222..387a65c785 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -273,7 +273,6 @@ "status.direct": "Palihim na banggitin si/ang @{name}", "status.direct_indicator": "Palihim na banggit", "status.edit": "Baguhin", - "status.edited": "Binago noong {date}", "status.edited_x_times": "Binago {count, plural, one {{count} beses} other {{count} na beses}}", "status.history.created": "Nilikha ni/ng {name} {date}", "status.history.edited": "Binago ni/ng {name} {date}", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index daaefea86d..53677e2383 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -662,10 +662,11 @@ "status.direct": "Umrøð @{name} privat", "status.direct_indicator": "Privat umrøða", "status.edit": "Rætta", - "status.edited": "Rættað {date}", + "status.edited": "Seinast broytt {date}", "status.edited_x_times": "Rættað {count, plural, one {{count} ferð} other {{count} ferð}}", "status.embed": "Legg inní", "status.favourite": "Dámdur postur", + "status.favourites": "{count, plural, one {yndispostur} other {yndispostar}}", "status.filter": "Filtrera hendan postin", "status.filtered": "Filtrerað", "status.hide": "Fjal post", @@ -686,6 +687,7 @@ "status.reblog": "Stimbra", "status.reblog_private": "Stimbra við upprunasýni", "status.reblogged_by": "{name} stimbrað", + "status.reblogs": "{count, plural, one {stimbran} other {stimbranir}}", "status.reblogs.empty": "Eingin hevur stimbrað hendan postin enn. Tá onkur stimbrar postin, verður hann sjónligur her.", "status.redraft": "Strika & ger nýggja kladdu", "status.remove_bookmark": "Gloym", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 8aff0b5afe..6f517fb34e 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -646,7 +646,6 @@ "status.direct": "Mention privée @{name}", "status.direct_indicator": "Mention privée", "status.edit": "Modifier", - "status.edited": "Modifiée le {date}", "status.edited_x_times": "Modifiée {count, plural, one {{count} fois} other {{count} fois}}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 223878f6f9..81c6719ea5 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -646,7 +646,6 @@ "status.direct": "Mention privée @{name}", "status.direct_indicator": "Mention privée", "status.edit": "Modifier", - "status.edited": "Modifié le {date}", "status.edited_x_times": "Modifié {count, plural, one {{count} fois} other {{count} fois}}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 5757f2cde7..02b3833701 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -646,7 +646,6 @@ "status.direct": "Privee fermelde @{name}", "status.direct_indicator": "Priveefermelding", "status.edit": "Bewurkje", - "status.edited": "Bewurke op {date}", "status.edited_x_times": "{count, plural, one {{count} kear} other {{count} kearen}} bewurke", "status.embed": "Ynslute", "status.favourite": "Favoryt", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 44af2fb4f6..50ea6bbf6c 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -458,7 +458,6 @@ "status.copy": "Copy link to status", "status.delete": "Scrios", "status.edit": "Cuir in eagar", - "status.edited": "Curtha in eagar in {date}", "status.edited_x_times": "Curtha in eagar {count, plural, one {{count} uair amháin} two {{count} uair} few {{count} uair} many {{count} uair} other {{count} uair}}", "status.embed": "Leabaigh", "status.filter": "Déan scagadh ar an bpostáil seo", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index ae190a51b0..5d263c0fe6 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -646,7 +646,6 @@ "status.direct": "Thoir iomradh air @{name} gu prìobhaideach", "status.direct_indicator": "Iomradh prìobhaideach", "status.edit": "Deasaich", - "status.edited": "Air a dheasachadh {date}", "status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{counter} turas} two {{counter} thuras} few {{counter} tursan} other {{counter} turas}}", "status.embed": "Leabaich", "status.favourite": "Cuir ris na h-annsachdan", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index e3928b5f3b..02f57570a5 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -662,10 +662,11 @@ "status.direct": "Mencionar de xeito privado a @{name}", "status.direct_indicator": "Mención privada", "status.edit": "Editar", - "status.edited": "Editado {date}", + "status.edited": "Última edición {date}", "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Incrustar", "status.favourite": "Favorecer", + "status.favourites": "{count, plural, one {favorecemento} other {favorecementos}}", "status.filter": "Filtrar esta publicación", "status.filtered": "Filtrado", "status.hide": "Agochar publicación", @@ -686,6 +687,7 @@ "status.reblog": "Promover", "status.reblog_private": "Compartir coa audiencia orixinal", "status.reblogged_by": "{name} promoveu", + "status.reblogs": "{count, plural, one {promoción} other {promocións}}", "status.reblogs.empty": "Aínda ninguén promoveu esta publicación. Cando alguén o faga, amosarase aquí.", "status.redraft": "Eliminar e reescribir", "status.remove_bookmark": "Eliminar marcador", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 53e2653130..6cf7fc9283 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -662,10 +662,11 @@ "status.direct": "הודעה פרטית אל @{name}", "status.direct_indicator": "הודעה פרטית", "status.edit": "עריכה", - "status.edited": "נערך ב{date}", + "status.edited": "נערך לאחרונה {date}", "status.edited_x_times": "נערך {count, plural, one {פעם {count}} other {{count} פעמים}}", "status.embed": "הטמעה", "status.favourite": "חיבוב", + "status.favourites": "{count, plural, one {חיבוב אחד} two {זוג חיבובים} other {# חיבובים}}", "status.filter": "סנן הודעה זו", "status.filtered": "סונן", "status.hide": "הסתרת חיצרוץ", @@ -686,6 +687,7 @@ "status.reblog": "הדהוד", "status.reblog_private": "להדהד ברמת הנראות המקורית", "status.reblogged_by": "{name} הידהד/ה:", + "status.reblogs": "{count, plural, one {הדהוד אחד} two {שני הדהודים} other {# הדהודים}}", "status.reblogs.empty": "עוד לא הידהדו את ההודעה הזו. כאשר זה יקרה, ההדהודים יופיעו כאן.", "status.redraft": "מחיקה ועריכה מחדש", "status.remove_bookmark": "הסרת סימניה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 45d1b53268..1e68854c12 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -475,7 +475,6 @@ "status.copy": "Copy link to status", "status.delete": "Obriši", "status.edit": "Uredi", - "status.edited": "Uređeno {date}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.embed": "Umetni", "status.filter": "Filtriraj ovu objavu", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 1cbca5654b..f658fe4656 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -662,10 +662,11 @@ "status.direct": "@{name} személyes említése", "status.direct_indicator": "Személyes említés", "status.edit": "Szerkesztés", - "status.edited": "Szerkesztve: {date}", + "status.edited": "Utoljára szerkesztve {date}", "status.edited_x_times": "{count, plural, one {{count} alkalommal} other {{count} alkalommal}} szerkesztve", "status.embed": "Beágyazás", "status.favourite": "Kedvenc", + "status.favourites": "{count, plural, one {kedvenc} other {kedvenc}}", "status.filter": "E bejegyzés szűrése", "status.filtered": "Megszűrt", "status.hide": "Bejegyzés elrejtése", @@ -686,6 +687,7 @@ "status.reblog": "Megtolás", "status.reblog_private": "Megtolás az eredeti közönségnek", "status.reblogged_by": "{name} megtolta", + "status.reblogs": "{count, plural, one {megtolás} other {megtolás}}", "status.reblogs.empty": "Senki sem tolta még meg ezt a bejegyzést. Ha valaki megteszi, itt fog megjelenni.", "status.redraft": "Törlés és újraírás", "status.remove_bookmark": "Könyvjelző eltávolítása", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 67a7aa49ff..2ef6d4c777 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -467,7 +467,6 @@ "status.direct": "Մասնաւոր յիշատակում @{name}", "status.direct_indicator": "Մասնաւոր յիշատակում", "status.edit": "Խմբագրել", - "status.edited": "Խմբագրուել է՝ {date}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.embed": "Ներդնել", "status.favourite": "Հավանել", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index a935bf1112..89285271ff 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -366,7 +366,6 @@ "status.direct": "Mentionar privatemente a @{name}", "status.direct_indicator": "Mention private", "status.edit": "Modificar", - "status.edited": "Modificate le {date}", "status.edited_x_times": "Modificate {count, plural, one {{count} tempore} other {{count} tempores}}", "status.favourite": "Adder al favoritos", "status.filter": "Filtrar iste message", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 9b841a08bb..ae2051a376 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -523,7 +523,6 @@ "status.delete": "Hapus", "status.detailed_status": "Tampilan detail percakapan", "status.edit": "Edit", - "status.edited": "Diedit {date}", "status.edited_x_times": "Diedit {count, plural, other {{count} kali}}", "status.embed": "Tanam", "status.filter": "Saring kiriman ini", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index 1f60b42400..d0a42b0b35 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -646,7 +646,6 @@ "status.direct": "Privatmen mentionar @{name}", "status.direct_indicator": "Privat mention", "status.edit": "Modificar", - "status.edited": "Modificat ye {date}", "status.edited_x_times": "Modificat {count, plural, one {{count} vez} other {{count} vezes}}", "status.embed": "Inbedar", "status.favourite": "Favoritisar", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 235b6b92af..5d0b231ed1 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -610,7 +610,6 @@ "status.direct": "Private mencionez @{name}", "status.direct_indicator": "Privata menciono", "status.edit": "Modifikez", - "status.edited": "Modifikesis ye {date}", "status.edited_x_times": "Modifikesis {count, plural, one {{count} foyo} other {{count} foyi}}", "status.embed": "Eninsertez", "status.favourite": "Favorizar", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 2310325b7c..cf2d0281f8 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -662,10 +662,11 @@ "status.direct": "Einkaspjall við @{name}", "status.direct_indicator": "Einkaspjall", "status.edit": "Breyta", - "status.edited": "Breytt {date}", + "status.edited": "Síðast breytt {date}", "status.edited_x_times": "Breytt {count, plural, one {{count} sinni} other {{count} sinnum}}", "status.embed": "Ívefja", "status.favourite": "Eftirlæti", + "status.favourites": "{count, plural, one {eftirlæti} other {eftirlæti}}", "status.filter": "Sía þessa færslu", "status.filtered": "Síað", "status.hide": "Fela færslu", @@ -686,6 +687,7 @@ "status.reblog": "Endurbirting", "status.reblog_private": "Endurbirta til upphaflegra lesenda", "status.reblogged_by": "{name} endurbirti", + "status.reblogs": "{count, plural, one {endurbirting} other {endurbirtingar}}", "status.reblogs.empty": "Enginn hefur ennþá endurbirt þessa færslu. Þegar einhver gerir það, mun það birtast hér.", "status.redraft": "Eyða og endurvinna drög", "status.remove_bookmark": "Fjarlægja bókamerki", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 56b637612d..06825f40dd 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -662,10 +662,11 @@ "status.direct": "Menziona privatamente @{name}", "status.direct_indicator": "Menzione privata", "status.edit": "Modifica", - "status.edited": "Modificato il {date}", + "status.edited": "Ultima modifica {date}", "status.edited_x_times": "Modificato {count, plural, one {{count} volta} other {{count} volte}}", "status.embed": "Incorpora", "status.favourite": "Preferito", + "status.favourites": "{count, plural, one {preferito} other {preferiti}}", "status.filter": "Filtra questo post", "status.filtered": "Filtrato", "status.hide": "Nascondi il post", @@ -686,6 +687,7 @@ "status.reblog": "Reblog", "status.reblog_private": "Reblog con visibilità originale", "status.reblogged_by": "Rebloggato da {name}", + "status.reblogs": "{count, plural, one {boost} other {boost}}", "status.reblogs.empty": "Ancora nessuno ha rebloggato questo post. Quando qualcuno lo farà, apparirà qui.", "status.redraft": "Elimina e riscrivi", "status.remove_bookmark": "Rimuovi segnalibro", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 872293b230..ba32c7c492 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -241,6 +241,7 @@ "empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しい投稿をするとここに表示されます。", "empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。", "empty_column.mutes": "まだ誰もミュートしていません。", + "empty_column.notification_requests": "ここに表示するものはありません。新しい通知を受け取ったとき、フィルタリング設定で通知がブロックされたアカウントがある場合はここに表示されます。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう", "error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。", @@ -271,6 +272,8 @@ "filter_modal.select_filter.subtitle": "既存のカテゴリーを使用するか新規作成します", "filter_modal.select_filter.title": "この投稿をフィルターする", "filter_modal.title.status": "投稿をフィルターする", + "filtered_notifications_banner.pending_requests": "{count, plural, =0 {アカウント} other {#アカウント}}からの通知がブロックされています", + "filtered_notifications_banner.title": "ブロック済みの通知", "firehose.all": "すべて", "firehose.local": "このサーバー", "firehose.remote": "ほかのサーバー", @@ -439,6 +442,10 @@ "notification.reblog": "{name}さんがあなたの投稿をブーストしました", "notification.status": "{name}さんが投稿しました", "notification.update": "{name}さんが投稿を編集しました", + "notification_requests.accept": "受け入れる", + "notification_requests.dismiss": "無視", + "notification_requests.notifications_from": "{name}からの通知", + "notification_requests.title": "ブロック済みの通知", "notifications.clear": "通知を消去", "notifications.clear_confirmation": "本当に通知を消去しますか?", "notifications.column_settings.admin.report": "新しい通報:", @@ -470,6 +477,15 @@ "notifications.permission_denied": "ブラウザの通知が拒否されているためデスクトップ通知は利用できません", "notifications.permission_denied_alert": "ブラウザの通知が拒否されているためデスクトップ通知を有効にできません", "notifications.permission_required": "必要な権限が付与されていないため、デスクトップ通知は利用できません。", + "notifications.policy.filter_new_accounts.hint": "作成から{days, plural, other {#日}}以内のアカウントからの通知がブロックされます", + "notifications.policy.filter_new_accounts_title": "新しいアカウントからの通知をブロックする", + "notifications.policy.filter_not_followers_hint": "フォローされていても、フォローから{days, plural, other {#日}}経っていない場合はブロックされます", + "notifications.policy.filter_not_followers_title": "フォローされていないアカウントからの通知をブロックする", + "notifications.policy.filter_not_following_hint": "手動で受け入れたアカウントはブロックされません", + "notifications.policy.filter_not_following_title": "フォローしていないアカウントからの通知をブロックする", + "notifications.policy.filter_private_mentions_hint": "あなたがメンションした相手からの返信、およびフォローしているアカウントからの返信以外がブロックされます", + "notifications.policy.filter_private_mentions_title": "外部からの非公開の返信をブロックする", + "notifications.policy.title": "通知のフィルタリング", "notifications_permission_banner.enable": "デスクトップ通知を有効にする", "notifications_permission_banner.how_to_control": "Mastodonを閉じている間でも通知を受信するにはデスクトップ通知を有効にしてください。有効にすると上の {icon} ボタンから通知の内容を細かくカスタマイズできます。", "notifications_permission_banner.title": "お見逃しなく", @@ -646,7 +662,6 @@ "status.direct": "@{name}さんに非公開で投稿", "status.direct_indicator": "非公開の返信", "status.edit": "編集", - "status.edited": "{date}に編集", "status.edited_x_times": "{count}回編集", "status.embed": "埋め込み", "status.favourite": "お気に入り", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index db770b427a..0d719b71b1 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -48,7 +48,7 @@ "account.posts_with_replies": "Tisuffaɣ d tririyin", "account.report": "Cetki ɣef @{name}", "account.requested": "Di laɛḍil ad yettwaqbel. Ssit i wakken ad yefsex usuter n uḍfar", - "account.requested_follow": "{name} yessuter ad k-yeḍfer", + "account.requested_follow": "{name} yessuter ad k·m-yeḍfer", "account.share": "Bḍu amaɣnu n @{name}", "account.show_reblogs": "Ssken-d inebḍa n @{name}", "account.statuses_counter": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}", @@ -190,13 +190,13 @@ "empty_column.bookmarked_statuses": "Ulac kra n tsuffeɣt i terniḍ ɣer yismenyifen-ik·im ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.", "empty_column.community": "Tasuddemt tazayezt tadigant n yisallen d tilemt. Aru ihi kra akken ad tt-teččareḍ!", "empty_column.domain_blocks": "Ulac kra n taɣult yettwaffren ar tura.", - "empty_column.follow_requests": "Ulac ɣur-k ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.", + "empty_column.follow_requests": "Ulac ɣur-k·m ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.", "empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaɣ ɣer uhacṭag-agi.", "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.", "empty_column.list": "Ar tura ur yelli kra deg umuɣ-a. Ad d-yettwasken da ticki iɛeggalen n wumuɣ-a suffɣen-d kra.", - "empty_column.lists": "Ulac ɣur-k kra n wumuɣ yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.", - "empty_column.mutes": "Ulac ɣur-k imseqdacen i yettwasgugmen.", - "empty_column.notifications": "Ulac ɣur-k tilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", + "empty_column.lists": "Ulac ɣur-k·m kra n wumuɣ yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.", + "empty_column.mutes": "Ulac ɣur-k·m imseqdacen i yettwasgugmen.", + "empty_column.notifications": "Ulac ɣur-k·m tilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", "empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt", "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Maṣṭudun deg yiminig-nniḍen neɣ deg usnas anaṣli.", "errors.unexpected_crash.copy_stacktrace": "Nɣel stacktrace ɣef wafus", @@ -209,6 +209,7 @@ "explore.trending_tags": "Ihacṭagen", "filter_modal.added.review_and_configure_title": "Iɣewwaṛen n imzizdig", "filter_modal.added.settings_link": "asebter n yiɣewwaṛen", + "filter_modal.select_filter.expired": "yemmut", "filter_modal.select_filter.prompt_new": "Taggayt tamaynutt : {name}", "filter_modal.select_filter.search": "Nadi neɣ snulfu-d", "firehose.all": "Akk", @@ -217,6 +218,7 @@ "follow_request.authorize": "Ssireg", "follow_request.reject": "Agi", "follow_suggestions.dismiss": "Ur ttɛawad ara ad t-id-sekneṭ", + "follow_suggestions.view_all": "Wali-ten akk", "follow_suggestions.who_to_follow": "Menhu ara ḍefṛeḍ", "followed_tags": "Ihacṭagen yettwaḍfaren", "footer.about": "Ɣef", @@ -259,6 +261,7 @@ "interaction_modal.sign_in": "Ur tekcimeḍ ara ɣer uqeddac-a. Anda yella umiḍan-ik·im ?", "interaction_modal.sign_in_hint": "Ihi : Wa d asmel ideg tjerdeḍ. Ma ur tecfiḍ ara, nadi imayl n ummager deg tenkult-ik·im. Tzemreḍ daɣen ad d-tefkeḍ isem-ik·im n useqdac ummid ! (amedya @Mastodon@mastodon.social)", "interaction_modal.title.follow": "Ḍfer {name}", + "interaction_modal.title.reply": "Tiririt i tsuffeɣt n {name}", "intervals.full.days": "{number, plural, one {# n wass} other {# n wussan}}", "intervals.full.hours": "{number, plural, one {# n usarag} other {# n yesragen}}", "intervals.full.minutes": "{number, plural, one {# n tesdat} other {# n tesdatin}}", @@ -271,6 +274,7 @@ "keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.down": "i kennu ɣer wadda n tebdart", "keyboard_shortcuts.enter": "i tildin n tsuffeɣt", + "keyboard_shortcuts.favourites": "Ldi tabdert n yismenyifen", "keyboard_shortcuts.federated": "i tildin n tsuddemt tamatut n yisallen", "keyboard_shortcuts.heading": "Inegzumen n unasiw", "keyboard_shortcuts.home": "i tildin n tsuddemt tagejdant n yisallen", @@ -299,6 +303,7 @@ "lightbox.expand": "Simeɣer tamnaḍt n uskan n tugna", "lightbox.next": "Ɣer zdat", "lightbox.previous": "Ɣer deffir", + "limited_account_hint.action": "Wali amaɣnu akken yebɣu yili", "link_preview.author": "S-ɣur {name}", "lists.account.add": "Rnu ɣer tebdart", "lists.account.remove": "Kkes seg tebdart", @@ -344,12 +349,15 @@ "navigation_bar.security": "Taɣellist", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", "notification.follow": "iṭṭafar-ik·em-id {name}", - "notification.follow_request": "{name} yessuter-d ad k-yeḍfeṛ", + "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ", "notification.mention": "{name} yebder-ik-id", "notification.own_poll": "Tafrant-ik·im tfuk", "notification.poll": "Tfukk tefrant ideg tettekkaḍ", "notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen", "notification.status": "{name} akken i d-yessufeɣ", + "notification_requests.accept": "Qbel", + "notification_requests.dismiss": "Agi", + "notification_requests.notifications_from": "Ilɣa sɣur {name}", "notifications.clear": "Sfeḍ tilɣa", "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-inek·em i lebda?", "notifications.column_settings.alert": "Tilɣa n tnarit", @@ -374,6 +382,14 @@ "notifications.group": "{count} n tilɣa", "notifications.mark_as_read": "Creḍ meṛṛa iilɣa am wakken ttwaɣran", "notifications.permission_denied": "D awezɣi ad yili wermad n yilɣa n tnarit axateṛ turagt tettwagdel.", + "notifications.policy.filter_new_accounts.hint": "Imiḍanen imaynuten i d-yennulfan deg {days, plural, one {yiwen n wass} other {# n wussan}} yezrin", + "notifications.policy.filter_new_accounts_title": "Imiḍan imaynuten", + "notifications.policy.filter_not_followers_hint": "Ula d wid akked tid i k·m-id-iḍefren, ur wwiḍen ara {days, plural, one {yiwen n wass} other {# n wussan}}", + "notifications.policy.filter_not_followers_title": "Wid akked tid ur k·m-id-yeṭṭafaren ara", + "notifications.policy.filter_not_following_hint": "Alamma tqebleḍ-ten s ufus", + "notifications.policy.filter_not_following_title": "Wid akked tid ur tettḍafareḍ ara", + "notifications.policy.filter_private_mentions_title": "Abdar uslig ur yettwasferken ara", + "notifications.policy.title": "Sizdeg ilɣa seg …", "notifications_permission_banner.enable": "Rmed talɣutin n tnarit", "notifications_permission_banner.title": "Ur zeggel acemma", "onboarding.action.back": "Tuɣalin ɣer deffir", @@ -384,6 +400,7 @@ "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.title": "Popular on Mastodon", "onboarding.profile.display_name": "Isem ara d-yettwaskanen", + "onboarding.profile.note": "Tameddurt", "onboarding.profile.note_hint": "Tzemreḍ ad d-@tbedreḍ imdanen niḍen neɣ #ihacṭagen …", "onboarding.profile.save_and_continue": "Sekles, tkemmleḍ", "onboarding.profile.title": "Asbadu n umaɣnu", @@ -414,15 +431,16 @@ "poll_button.add_poll": "Rnu asenqed", "poll_button.remove_poll": "Kkes asenqed", "privacy.change": "Seggem tabaḍnit n yizen", - "privacy.direct.long": "Wid akk i d-yettwabdaren deg tuffeɣt", - "privacy.direct.short": "Imdanen ulmisen", - "privacy.private.long": "Ala wid i k-yeṭṭafaṛen", + "privacy.direct.long": "Wid akk i d-yettwabdaren deg tsuffeɣt", + "privacy.direct.short": "Imdanen yettwafernen", + "privacy.private.long": "Ala wid i k·m-yeṭṭafaṛen", "privacy.private.short": "Imeḍfaren", "privacy.public.long": "Kra n win yellan deg Masṭudun neɣ berra-s", "privacy.public.short": "Azayez", "privacy.unlisted.long": "Kra kan n ilguritmen", "privacy_policy.last_updated": "Aleqqem aneggaru {date}", "privacy_policy.title": "Tasertit tabaḍnit", + "recommended": "Yettuwelleh", "refresh": "Smiren", "regeneration_indicator.label": "Yessalay-d…", "regeneration_indicator.sublabel": "Tasuddemt tagejdant ara d-tettwaheggay!", @@ -487,7 +505,6 @@ "status.copy": "Nɣel assaɣ ɣer tasuffeɣt", "status.delete": "Kkes", "status.edit": "Ẓreg", - "status.edited": "Tettwaẓreg deg {date}", "status.edited_x_times": "Tettwaẓreg {count, plural, one {{count} n tikkelt} other {{count} n tikkal}}", "status.embed": "Seddu", "status.filtered": "Yettwasizdeg", @@ -507,6 +524,7 @@ "status.reblogs.empty": "Ula yiwen ur yebḍi tajewwiqt-agi ar tura. Ticki yebḍa-tt yiwen, ad d-iban da.", "status.redraft": "Kkes tɛiwdeḍ tira", "status.remove_bookmark": "Kkes tacreḍt", + "status.replied_to": "Y·terra-yas i {name}", "status.reply": "Err", "status.replyAll": "Err i lxiḍ", "status.report": "Cetki ɣef @{name}", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 642a3f4ae1..a96b146ac2 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -660,7 +660,6 @@ "status.direct": "@{name} 님에게 개인적으로 멘션", "status.direct_indicator": "개인적인 멘션", "status.edit": "수정", - "status.edited": "{date}에 수정함", "status.edited_x_times": "{count}번 수정됨", "status.embed": "임베드", "status.favourite": "좋아요", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index a106101081..0702fa0003 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -518,7 +518,6 @@ "status.direct": "Bi taybetî qale @{name} bike", "status.direct_indicator": "Qalkirinê taybet", "status.edit": "Serrast bike", - "status.edited": "Di {date} de hate serrastkirin", "status.edited_x_times": "{count, plural, one {{count} car} other {{count} car}} hate serrastkirin", "status.embed": "Bi cih bike", "status.filter": "Vê şandiyê parzûn bike", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index 698b3da4c3..99aac2ef13 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -139,7 +139,6 @@ "status.copy": "Copy link to status", "status.delete": "Oblitterare", "status.edit": "Recolere", - "status.edited": "Recultum {date}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.open": "Expand this status", "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index e23f225928..9a85344923 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -659,7 +659,6 @@ "status.direct": "Enmenta a @{name} en privado", "status.direct_indicator": "Enmentadura privada", "status.edit": "Edita", - "status.edited": "Editado {date}", "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} vezes}}", "status.embed": "Inkrusta", "status.favourite": "Te plaze", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 326560c888..1503460978 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -656,10 +656,11 @@ "status.direct": "Privačiai paminėti @{name}", "status.direct_indicator": "Privatus paminėjimas", "status.edit": "Redaguoti", - "status.edited": "Redaguota {date}", + "status.edited": "Paskutinį kartą redaguota {date}", "status.edited_x_times": "Redaguota {count, plural, one {{count} kartą} few {{count} kartus} many {{count} karto} other {{count} kartų}}", "status.embed": "Įterpti", "status.favourite": "Pamėgti", + "status.favourites": "{count, plural, one {mėgstamas} few {mėgstamai} many {mėgstamų} other {mėgstamų}}", "status.filter": "Filtruoti šį įrašą", "status.filtered": "Filtruota", "status.hide": "Slėpti įrašą", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 47f10d94c1..726ae655c2 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -623,15 +623,14 @@ "status.admin_status": "Atvērt šo ziņu moderācijas saskarnē", "status.block": "Bloķēt @{name}", "status.bookmark": "Grāmatzīme", - "status.cancel_reblog_private": "Neizcelt", + "status.cancel_reblog_private": "Nepastiprināt", "status.cannot_reblog": "Šo ziņu nevar izcelt", - "status.copy": "Kopēt saiti uz ziņu", + "status.copy": "Ievietot ieraksta saiti starpliktuvē", "status.delete": "Dzēst", "status.detailed_status": "Detalizēts sarunas skats", "status.direct": "Pieminēt @{name} privāti", "status.direct_indicator": "Pieminēts privāti", "status.edit": "Labot", - "status.edited": "Labots {date}", "status.edited_x_times": "Labots {count, plural, one {{count} reizi} other {{count} reizes}}", "status.embed": "Iestrādāt", "status.favourite": "Iecienīts", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 20bd248c3a..155eeccee6 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -632,7 +632,6 @@ "status.direct": "Sebut secara peribadi @{name}", "status.direct_indicator": "Sebutan peribadi", "status.edit": "Sunting", - "status.edited": "Disunting {date}", "status.edited_x_times": "Disunting {count, plural, other {{count} kali}}", "status.embed": "Benaman", "status.favourite": "Kegemaran", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index 1b129aaa0f..001e15e0d1 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -610,7 +610,6 @@ "status.direct": "@{name} ကို သီးသန့်ဖော်ပြမည်\n", "status.direct_indicator": "သီးသန့်ဖော်ပြခြင်း။", "status.edit": "ပြင်ဆင်ရန်", - "status.edited": "{date} ကို ပြင်ဆင်ပြီးပါပြီ", "status.edited_x_times": "{count, plural, one {{count} time} other {{count} times}} ပြင်ဆင်ခဲ့သည်", "status.embed": "Embed", "status.favourite": "Favorite", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 34a0c5ea5a..cdb5562e90 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -662,10 +662,11 @@ "status.direct": "@{name} een privébericht sturen", "status.direct_indicator": "Privébericht", "status.edit": "Bewerken", - "status.edited": "Bewerkt op {date}", + "status.edited": "Laatste bewerking op {date}", "status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt", "status.embed": "Embedden", "status.favourite": "Favoriet", + "status.favourites": "{count, plural, one {# favoriet} other {# favorieten}}", "status.filter": "Dit bericht filteren", "status.filtered": "Gefilterd", "status.hide": "Bericht verbergen", @@ -686,6 +687,7 @@ "status.reblog": "Boosten", "status.reblog_private": "Boost naar oorspronkelijke ontvangers", "status.reblogged_by": "{name} boostte", + "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.", "status.redraft": "Verwijderen en herschrijven", "status.remove_bookmark": "Bladwijzer verwijderen", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 94fee3498d..4235f57916 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -662,7 +662,6 @@ "status.direct": "Nevn @{name} privat", "status.direct_indicator": "Privat omtale", "status.edit": "Rediger", - "status.edited": "Redigert {date}", "status.edited_x_times": "Redigert {count, plural, one {{count} gong} other {{count} gonger}}", "status.embed": "Bygg inn", "status.favourite": "Favoritt", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 14e33e9f18..4834100a91 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -634,7 +634,6 @@ "status.direct": "Nevn @{name} privat", "status.direct_indicator": "Privat omtale", "status.edit": "Rediger", - "status.edited": "Redigert {date}", "status.edited_x_times": "Redigert {count, plural,one {{count} gang} other {{count} ganger}}", "status.embed": "Bygge inn", "status.favourite": "Favoritt", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 09276b3b32..2a5d05aac7 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -525,7 +525,6 @@ "status.direct": "Mencionar @{name} en privat", "status.direct_indicator": "Mencion privada", "status.edit": "Modificar", - "status.edited": "Modificat {date}", "status.edited_x_times": "Modificat {count, plural, un {{count} còp} other {{count} còps}}", "status.embed": "Embarcar", "status.favourite": "Apondre als favorits", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index 69360184fc..f86a6933fc 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -310,7 +310,6 @@ "status.copy": "ਪੋਸਟ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ", "status.delete": "ਹਟਾਓ", "status.edit": "ਸੋਧ", - "status.edited": "{date} ਨੂੰ ਸੋਧਿਆ", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.embed": "ਮੜ੍ਹੋ", "status.favourite": "ਪਸੰਦ", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 51f705b692..acd389da1c 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -662,10 +662,11 @@ "status.direct": "Prywatna wzmianka @{name}", "status.direct_indicator": "Prywatna wzmianka", "status.edit": "Edytuj", - "status.edited": "Edytowano {date}", + "status.edited": "Ostatnio edytowane {date}", "status.edited_x_times": "Edytowano {count, plural, one {{count} raz} other {{count} razy}}", "status.embed": "Osadź", "status.favourite": "Dodaj do ulubionych", + "status.favourites": "{count, plural, one {polubienie} few {polubienia} other {polubień}}", "status.filter": "Filtruj ten wpis", "status.filtered": "Filtrowany(-a)", "status.hide": "Ukryj post", @@ -686,6 +687,7 @@ "status.reblog": "Podbij", "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu", "status.reblogged_by": "Podbite przez {name}", + "status.reblogs": "{count, plural, one {podbicie} few {podbicia} other {podbić}}", "status.reblogs.empty": "Nikt nie podbił jeszcze tego wpisu. Gdy ktoś to zrobi, pojawi się tutaj.", "status.redraft": "Usuń i przeredaguj", "status.remove_bookmark": "Usuń zakładkę", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index a0b66e74e7..585052de91 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -241,6 +241,7 @@ "empty_column.list": "Nada aqui. Quando membros da lista tootarem, eles aparecerão aqui.", "empty_column.lists": "Nada aqui. Quando você criar listas, elas aparecerão aqui.", "empty_column.mutes": "Nada aqui.", + "empty_column.notification_requests": "Tudo limpo! Não há nada aqui. Quando você receber novas notificações, elas aparecerão aqui de acordo com suas configurações.", "empty_column.notifications": "Interaja com outros usuários para começar a conversar.", "empty_column.public": "Publique algo ou siga manualmente usuários de outros servidores", "error.unexpected_crash.explanation": "Esta página não pôde ser mostrada corretamente. Este erro provavelmente é devido a um bug em nosso código ou um problema de compatibilidade de navegador.", @@ -271,13 +272,20 @@ "filter_modal.select_filter.subtitle": "Use uma categoria existente ou crie uma nova", "filter_modal.select_filter.title": "Filtrar esta publicação", "filter_modal.title.status": "Filtrar uma publicação", + "filtered_notifications_banner.title": "Notificações filtradas", "firehose.all": "Tudo", "firehose.local": "Este servidor", "firehose.remote": "Outros servidores", "follow_request.authorize": "Aprovar", "follow_request.reject": "Recusar", "follow_requests.unlocked_explanation": "Apesar de seu perfil não ser trancado, {domain} exige que você revise a solicitação para te seguir destes perfis manualmente.", + "follow_suggestions.curated_suggestion": "Escolha da equipe", "follow_suggestions.dismiss": "Não mostrar novamente", + "follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.", + "follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.", + "follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.", + "follow_suggestions.hints.most_interactions": "Este perfil tem recebido recentemente muita atenção em {domain}.", + "follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.", "follow_suggestions.personalized_suggestion": "Sugestão personalizada", "follow_suggestions.popular_suggestion": "Sugestão popular", "follow_suggestions.view_all": "Visualizar tudo", @@ -433,6 +441,10 @@ "notification.reblog": "{name} deu boost no teu toot", "notification.status": "{name} acabou de tootar", "notification.update": "{name} editou uma publicação", + "notification_requests.accept": "Aceitar", + "notification_requests.dismiss": "Rejeitar", + "notification_requests.notifications_from": "Notificações de {name}", + "notification_requests.title": "Notificações filtradas", "notifications.clear": "Limpar notificações", "notifications.clear_confirmation": "Você tem certeza de que deseja limpar todas as suas notificações?", "notifications.column_settings.admin.report": "Novas denúncias:", @@ -464,6 +476,13 @@ "notifications.permission_denied": "Navegador não tem permissão para ativar notificações no computador.", "notifications.permission_denied_alert": "Verifique a permissão do navegador para ativar notificações no computador.", "notifications.permission_required": "Ativar notificações no computador exige permissão do navegador.", + "notifications.policy.filter_new_accounts_title": "Novas contas", + "notifications.policy.filter_not_followers_title": "Pessoas que não estão te seguindo", + "notifications.policy.filter_not_following_hint": "Até que você os aprove manualmente", + "notifications.policy.filter_not_following_title": "Pessoas que você não segue", + "notifications.policy.filter_private_mentions_hint": "Filtrado, a menos que respondido em sua própria menção ou se você segue o remetente", + "notifications.policy.filter_private_mentions_title": "Menções privadas não solicitadas", + "notifications.policy.title": "Filtrar notificações de…", "notifications_permission_banner.enable": "Ativar notificações no computador", "notifications_permission_banner.how_to_control": "Para receber notificações quando o Mastodon não estiver aberto, ative as notificações no computador. Você pode controlar precisamente quais tipos de interações geram notificações no computador através do botão {icon}.", "notifications_permission_banner.title": "Nunca perca nada", @@ -525,6 +544,9 @@ "privacy.private.short": "Seguidores", "privacy.public.long": "Qualquer um dentro ou fora do Mastodon", "privacy.public.short": "Público", + "privacy.unlisted.additional": "Isso se comporta exatamente como público, exceto que a publicação não aparecerá nos _feeds ao vivo_ ou nas _hashtags_, explorar, ou barra de busca, mesmo que você seja escolhido em toda a conta.", + "privacy.unlisted.long": "Menos notificações e recomendações do algoritmo", + "privacy.unlisted.short": "Público (silencioso)", "privacy_policy.last_updated": "Atualizado {date}", "privacy_policy.title": "Política de privacidade", "recommended": "Recomendado", @@ -636,7 +658,7 @@ "status.direct": "Mencione em privado @{name}", "status.direct_indicator": "Menção privada", "status.edit": "Editar", - "status.edited": "Editado em {date}", + "status.edited": "Última edição em {date}", "status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}", "status.embed": "Incorporar", "status.favourite": "Favorita", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 18a550d641..c5a837a34c 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -662,10 +662,11 @@ "status.direct": "Mencionar @{name} em privado", "status.direct_indicator": "Menção privada", "status.edit": "Editar", - "status.edited": "Editado em {date}", + "status.edited": "Última edição em {date}", "status.edited_x_times": "Editado {count, plural,one {{count} vez} other {{count} vezes}}", "status.embed": "Embutir", "status.favourite": "Assinalar como favorito", + "status.favourites": "{count, plural, one {favorito} other {favoritos}}", "status.filter": "Filtrar esta publicação", "status.filtered": "Filtrada", "status.hide": "Ocultar publicação", @@ -686,6 +687,7 @@ "status.reblog": "Partilhar", "status.reblog_private": "Partilhar com a visibilidade original", "status.reblogged_by": "{name} reforçou", + "status.reblogs": "{count, plural, one {partilha} other {partilhas}}", "status.reblogs.empty": "Ainda ninguém reforçou esta publicação. Quando alguém o fizer, ele irá aparecer aqui.", "status.redraft": "Apagar & reescrever", "status.remove_bookmark": "Retirar dos marcadores", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index f31499f845..37e999053c 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -576,7 +576,6 @@ "status.direct": "Menționează @{name} în privat", "status.direct_indicator": "Mențiune privată", "status.edit": "Modifică", - "status.edited": "Modificat în data de {date}", "status.edited_x_times": "Modificată {count, plural, one {o dată} few {de {count} ori} other {de {count} de ori}}", "status.embed": "Înglobează", "status.filter": "Filtrează această postare", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 000d485eda..917b8f4965 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -640,7 +640,6 @@ "status.direct": "Лично упоминать @{name}", "status.direct_indicator": "Личные упоминания", "status.edit": "Изменить", - "status.edited": "Последнее изменение: {date}", "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}", "status.embed": "Встроить на свой сайт", "status.favourite": "Избранное", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index 36dae25c17..8620b348e3 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -523,7 +523,6 @@ "status.delete": "मार्जय", "status.detailed_status": "विस्तृतसंभाषणदृश्यम्", "status.edit": "सम्पादय", - "status.edited": "सम्पादितं {date}", "status.edited_x_times": "Edited {count, plural, one {{count} वारम्} other {{count} वारम्}}", "status.embed": "निहितम्", "status.filter": "पत्रमिदं फिल्तरं कुरु", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index 7e9778b57c..7d6997131e 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -494,7 +494,6 @@ "status.delete": "Delete", "status.detailed_status": "Detailt conversation view", "status.edit": "Edit", - "status.edited": "Editit {date}", "status.edited_x_times": "Editit {count, plural, one {{count} time} other {{count} times}}", "status.embed": "Embed", "status.filter": "Filter this post", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index a2194b56cb..e310a63edc 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -404,7 +404,6 @@ "status.delete": "මකන්න", "status.detailed_status": "විස්තරාත්මක සංවාද දැක්ම", "status.edit": "සංස්කරණය", - "status.edited": "සංශෝධිතයි {date}", "status.edited_x_times": "සංශෝධිතයි {count, plural, one {වාර {count}} other {වාර {count}}}", "status.embed": "කාවැද්දූ", "status.filter": "මෙම ලිපිය පෙරන්න", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 07f7617358..499ce9794c 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -653,7 +653,6 @@ "status.direct": "Súkromne označiť @{name}", "status.direct_indicator": "Súkromné označenie", "status.edit": "Upraviť", - "status.edited": "Upravené {date}", "status.edited_x_times": "Upravený {count, plural, other {{count}×}}", "status.embed": "Vložiť", "status.favourite": "Ohviezdičkované", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 1fe7dd0786..c96e929163 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -662,10 +662,11 @@ "status.direct": "Zasebno omeni @{name}", "status.direct_indicator": "Zasebna omemba", "status.edit": "Uredi", - "status.edited": "Urejeno {date}", + "status.edited": "Zadnje urejanje {date}", "status.edited_x_times": "Urejeno {count, plural, one {#-krat} two {#-krat} few {#-krat} other {#-krat}}", "status.embed": "Vdelaj", "status.favourite": "Priljubljen_a", + "status.favourites": "{count, plural, one {priljubitev} two {priljubitvi} few {priljubitve} other {priljubitev}}", "status.filter": "Filtriraj to objavo", "status.filtered": "Filtrirano", "status.hide": "Skrij objavo", @@ -686,6 +687,7 @@ "status.reblog": "Izpostavi", "status.reblog_private": "Izpostavi z izvirno vidljivostjo", "status.reblogged_by": "{name} je izpostavil/a", + "status.reblogs": "{count, plural, one {izpostavitev} two {izpostavitvi} few {izpostavitve} other {izpostavitev}}", "status.reblogs.empty": "Nihče še ni izpostavil te objave. Ko se bo to zgodilo, se bodo pojavile tukaj.", "status.redraft": "Izbriši in preoblikuj", "status.remove_bookmark": "Odstrani zaznamek", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index beba09d59b..8c71055594 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -659,7 +659,6 @@ "status.direct": "Përmendje private për @{name}", "status.direct_indicator": "Përmendje private", "status.edit": "Përpunojeni", - "status.edited": "Përpunuar më {date}", "status.edited_x_times": "Përpunuar {count, plural, one {{count} herë} other {{count} herë}}", "status.embed": "Trupëzim", "status.favourite": "I vini shenjë si të parapëlqyer", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 6a5ad998d4..6281f729f7 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -662,10 +662,11 @@ "status.direct": "Privatno pomeni @{name}", "status.direct_indicator": "Privatno pominjanje", "status.edit": "Uredi", - "status.edited": "Uređeno {date}", + "status.edited": "Poslednje uređivanje {date}", "status.edited_x_times": "Uređeno {count, plural, one {{count} put} other {{count} puta}}", "status.embed": "Ugradi", "status.favourite": "Omiljeno", + "status.favourites": "{count, plural, one {# omiljeno} few {# omiljena} other {# omiljenih}}", "status.filter": "Filtriraj ovu objavu", "status.filtered": "Filtrirano", "status.hide": "Sakrij objavu", @@ -686,6 +687,7 @@ "status.reblog": "Podrži", "status.reblog_private": "Podrži sa originalnom vidljivošću", "status.reblogged_by": "{name} je podržao/la", + "status.reblogs": "{count, plural, one {# podržavanje} few {# podržavanja} other {# podržavanja}}", "status.reblogs.empty": "Još uvek niko nije podržao ovu objavu. Kada bude podržana, pojaviće se ovde.", "status.redraft": "Izbriši i preoblikuj", "status.remove_bookmark": "Ukloni obeleživač", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 9f96a2ccc0..ae550315e4 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -662,10 +662,11 @@ "status.direct": "Приватно помени @{name}", "status.direct_indicator": "Приватно помињање", "status.edit": "Уреди", - "status.edited": "Уређено {date}", + "status.edited": "Последње уређивање {date}", "status.edited_x_times": "Уређено {count, plural, one {{count} пут} other {{count} пута}}", "status.embed": "Угради", "status.favourite": "Омиљено", + "status.favourites": "{count, plural, one {# омиљено} few {# омиљена} other {# омиљених}}", "status.filter": "Филтрирај ову објаву", "status.filtered": "Филтрирано", "status.hide": "Сакриј објаву", @@ -686,6 +687,7 @@ "status.reblog": "Подржи", "status.reblog_private": "Подржи са оригиналном видљивошћу", "status.reblogged_by": "{name} је подржао/ла", + "status.reblogs": "{count, plural, one {# подржавање} few {# подржавања} other {# подржавања}}", "status.reblogs.empty": "Још увек нико није подржао ову објаву. Када буде подржана, појавиће се овде.", "status.redraft": "Избриши и преобликуј", "status.remove_bookmark": "Уклони обележивач", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 37886cd80f..6bf7471359 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -649,10 +649,11 @@ "status.direct": "Nämn @{name} privat", "status.direct_indicator": "Privat nämning", "status.edit": "Redigera", - "status.edited": "Ändrad {date}", + "status.edited": "Senast ändrad {date}", "status.edited_x_times": "Redigerad {count, plural, one {{count} gång} other {{count} gånger}}", "status.embed": "Bädda in", "status.favourite": "Favoritmarkera", + "status.favourites": "{count, plural, one {favorit} other {favoriter}}", "status.filter": "Filtrera detta inlägg", "status.filtered": "Filtrerat", "status.hide": "Dölj inlägg", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 28b1764510..d3a04209fd 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -662,7 +662,6 @@ "status.direct": "กล่าวถึง @{name} แบบส่วนตัว", "status.direct_indicator": "การกล่าวถึงแบบส่วนตัว", "status.edit": "แก้ไข", - "status.edited": "แก้ไขเมื่อ {date}", "status.edited_x_times": "แก้ไข {count, plural, other {{count} ครั้ง}}", "status.embed": "ฝัง", "status.favourite": "ชื่นชอบ", diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index 64c3f98db7..f6bf927b94 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -339,7 +339,6 @@ "status.cancel_reblog_private": "o pini e pana", "status.delete": "o weka", "status.edit": "o ante", - "status.edited": "ni li ante lon {date}", "status.embed": "ni o lon insa pi lipu ante", "status.favourite": "o suli", "status.hide": "o len", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 08fe40fe45..b5209c2dad 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -662,10 +662,11 @@ "status.direct": "@{name} kullanıcısına özelden değin", "status.direct_indicator": "Özel değinme", "status.edit": "Düzenle", - "status.edited": "{date} tarihinde düzenlenmiş", + "status.edited": "Son düzenleme {date}", "status.edited_x_times": "{count, plural, one {{count} kez} other {{count} kez}} düzenlendi", "status.embed": "Gömülü", "status.favourite": "Favori", + "status.favourites": "{count, plural, one {beğeni} other {beğeni}}", "status.filter": "Bu gönderiyi süzgeçle", "status.filtered": "Süzgeçlenmiş", "status.hide": "Gönderiyi gizle", @@ -686,6 +687,7 @@ "status.reblog": "Yeniden paylaş", "status.reblog_private": "Özgün görünürlük ile yeniden paylaş", "status.reblogged_by": "{name} yeniden paylaştı", + "status.reblogs": "{count, plural, one {yeniden paylaşım} other {yeniden paylaşım}}", "status.reblogs.empty": "Henüz hiç kimse bu Gönderiyi Yeniden Paylaşmadı. Herhangi bir kullanıcı yeniden paylaştığında burada görüntülenecek.", "status.redraft": "Sil,Düzenle ve Yeniden paylaş", "status.remove_bookmark": "Yer işaretini kaldır", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 49c7e9a145..ccddd82490 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -425,7 +425,6 @@ "status.delete": "Бетерү", "status.direct_indicator": "Хосусый искә алу", "status.edit": "Үзгәртү", - "status.edited": "{date} көнне төзәтте", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.embed": "Веб-биткә кертү", "status.filtered": "Сөзелгән", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 49d79d32d1..14cff9d82f 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -241,6 +241,7 @@ "empty_column.list": "Цей список порожній. Коли його учасники додадуть нові дописи, вони з'являться тут.", "empty_column.lists": "У вас ще немає списків. Коли ви їх створите, вони з'являться тут.", "empty_column.mutes": "Ви ще не приховали жодного користувача.", + "empty_column.notification_requests": "Усе чисто! Тут нічого немає. Коли ви отримаєте нові сповіщення, вони з'являться тут відповідно до ваших налаштувань.", "empty_column.notifications": "У вас ще немає сповіщень. Коли інші люди почнуть взаємодіяти з вами, ви побачите їх тут.", "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших серверів, щоб заповнити стрічку", "error.unexpected_crash.explanation": "Через помилку у нашому коді або несумісність браузера, ця сторінка не може бути зображена коректно.", @@ -271,6 +272,8 @@ "filter_modal.select_filter.subtitle": "Використати наявну категорію або створити нову", "filter_modal.select_filter.title": "Фільтрувати цей допис", "filter_modal.title.status": "Фільтрувати допис", + "filtered_notifications_banner.pending_requests": "Сповіщення від {count, plural, =0 {жодної особи} one {однієї особи} few {# осіб} many {# осіб} other {# особи}}, котрих ви можете знати", + "filtered_notifications_banner.title": "Відфільтровані сповіщення", "firehose.all": "Всі", "firehose.local": "Цей сервер", "firehose.remote": "Інші сервери", @@ -474,7 +477,11 @@ "notifications.permission_denied": "Сповіщення стільниці недоступні через раніше відхилений запит дозволів для браузера", "notifications.permission_denied_alert": "Сповіщення не можна ввімкнути оскільки у дозволі вже було відмовлено раніше", "notifications.permission_required": "Сповіщення на стільниці не доступні, оскільки необхідний дозвіл не надано.", + "notifications.policy.filter_new_accounts.hint": "Створено впродовж {days, plural, one {одного} few {# днів} many {# днів} other {# дня}}", "notifications.policy.filter_new_accounts_title": "Нові облікові записи", + "notifications.policy.filter_not_followers_title": "Люди не підписані на вас", + "notifications.policy.filter_not_following_hint": "Доки ви не схвалюєте їх вручну", + "notifications.policy.filter_not_following_title": "Люди, на яких ви не підписані", "notifications_permission_banner.enable": "Увімкнути сповіщення стільниці", "notifications_permission_banner.how_to_control": "Щоб отримувати сповіщення, коли Mastodon не відкрито, увімкніть сповіщення стільниці. Ви можете контролювати, які типи взаємодій створюють сповіщення через кнопку {icon} вгорі після їхнього увімкнення.", "notifications_permission_banner.title": "Не проґавте нічого", @@ -651,10 +658,11 @@ "status.direct": "Особиста згадка @{name}", "status.direct_indicator": "Особиста згадка", "status.edit": "Редагувати", - "status.edited": "Відредаговано {date}", + "status.edited": "Востаннє змінено {date}", "status.edited_x_times": "Відредаговано {count, plural, one {{count} раз} few {{count} рази} many {{counter} разів} other {{counter} разів}}", "status.embed": "Вбудувати", "status.favourite": "Уподобане", + "status.favourites": "{count, plural, one {вподобання} few {вподобання} many {вподобань} other {вподобання}}", "status.filter": "Фільтрувати цей допис", "status.filtered": "Відфільтровано", "status.hide": "Сховати допис", @@ -675,6 +683,7 @@ "status.reblog": "Поширити", "status.reblog_private": "Поширити для початкової аудиторії", "status.reblogged_by": "{name} поширює", + "status.reblogs": "{count, plural, one {поширення} few {поширення} many {поширень} other {поширення}}", "status.reblogs.empty": "Ніхто ще не поширив цей допис. Коли хтось це зроблять, вони будуть зображені тут.", "status.redraft": "Видалити та виправити", "status.remove_bookmark": "Видалити закладку", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 998aecd275..afd912556e 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -662,7 +662,6 @@ "status.direct": "Nhắn riêng @{name}", "status.direct_indicator": "Nhắn riêng", "status.edit": "Sửa", - "status.edited": "Đã sửa {date}", "status.edited_x_times": "Đã sửa {count, plural, other {{count} lần}}", "status.embed": "Nhúng", "status.favourite": "Thích", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 6d6cd4b6c7..7694270d10 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -662,10 +662,11 @@ "status.direct": "私下提及 @{name}", "status.direct_indicator": "私下提及", "status.edit": "编辑", - "status.edited": "编辑于 {date}", + "status.edited": "最近编辑于 {date}", "status.edited_x_times": "共编辑 {count, plural, one {{count} 次} other {{count} 次}}", "status.embed": "嵌入", "status.favourite": "喜欢", + "status.favourites": "{count, plural, other {次喜欢}}", "status.filter": "过滤此嘟文", "status.filtered": "已过滤", "status.hide": "隐藏嘟文", @@ -686,6 +687,7 @@ "status.reblog": "转嘟", "status.reblog_private": "转嘟(可见者不变)", "status.reblogged_by": "{name} 转嘟了", + "status.reblogs": "{count, plural, other {次转嘟}}", "status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。", "status.redraft": "删除并重新编辑", "status.remove_bookmark": "移除书签", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 74a5c5a2d6..3ec1085ec2 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -640,7 +640,6 @@ "status.direct": "私下提及 @{name}", "status.direct_indicator": "私人提及", "status.edit": "編輯", - "status.edited": "編輯於 {date}", "status.edited_x_times": "Edited {count, plural, one {{count} 次} other {{count} 次}}", "status.embed": "嵌入", "status.favourite": "最愛", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index ff0bd4c8ff..c2450ac015 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -662,10 +662,11 @@ "status.direct": "私訊 @{name}", "status.direct_indicator": "私訊", "status.edit": "編輯", - "status.edited": "編輯於 {date}", + "status.edited": "上次編輯於 {date}", "status.edited_x_times": "已編輯 {count, plural, one {{count} 次} other {{count} 次}}", "status.embed": "內嵌嘟文", "status.favourite": "最愛", + "status.favourites": "{count, plural, other {# 則最愛}}", "status.filter": "過濾此嘟文", "status.filtered": "已過濾", "status.hide": "隱藏嘟文", @@ -686,6 +687,7 @@ "status.reblog": "轉嘟", "status.reblog_private": "依照原嘟可見性轉嘟", "status.reblogged_by": "{name} 已轉嘟", + "status.reblogs": "{count, plural, other {# 則轉嘟}}", "status.reblogs.empty": "還沒有人轉嘟過這則嘟文。當有人轉嘟時,它將於此顯示。", "status.redraft": "刪除並重新編輯", "status.remove_bookmark": "移除書籤", diff --git a/app/javascript/mastodon/reducers/blocks.js b/app/javascript/mastodon/reducers/blocks.js deleted file mode 100644 index 1b65071634..0000000000 --- a/app/javascript/mastodon/reducers/blocks.js +++ /dev/null @@ -1,22 +0,0 @@ -import Immutable from 'immutable'; - -import { - BLOCKS_INIT_MODAL, -} from '../actions/blocks'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - account_id: null, - }), -}); - -export default function mutes(state = initialState, action) { - switch (action.type) { - case BLOCKS_INIT_MODAL: - return state.withMutations((state) => { - state.setIn(['new', 'account_id'], action.account.get('id')); - }); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 51a76d191e..3d42cc83f8 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -7,7 +7,6 @@ import { accountsReducer } from './accounts'; import accounts_map from './accounts_map'; import alerts from './alerts'; import announcements from './announcements'; -import blocks from './blocks'; import boosts from './boosts'; import compose from './compose'; import contexts from './contexts'; @@ -26,7 +25,6 @@ import markers from './markers'; import media_attachments from './media_attachments'; import meta from './meta'; import { modalReducer } from './modal'; -import mutes from './mutes'; import { notificationPolicyReducer } from './notification_policy'; import { notificationRequestsReducer } from './notification_requests'; import notifications from './notifications'; @@ -62,8 +60,6 @@ const reducers = { relationships: relationshipsReducer, settings, push_notifications, - mutes, - blocks, boosts, server, contexts, diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js deleted file mode 100644 index a9eb61ff83..0000000000 --- a/app/javascript/mastodon/reducers/mutes.js +++ /dev/null @@ -1,31 +0,0 @@ -import Immutable from 'immutable'; - -import { - MUTES_INIT_MODAL, - MUTES_TOGGLE_HIDE_NOTIFICATIONS, - MUTES_CHANGE_DURATION, -} from '../actions/mutes'; - -const initialState = Immutable.Map({ - new: Immutable.Map({ - account: null, - notifications: true, - duration: 0, - }), -}); - -export default function mutes(state = initialState, action) { - switch (action.type) { - case MUTES_INIT_MODAL: - return state.withMutations((state) => { - state.setIn(['new', 'account'], action.account); - state.setIn(['new', 'notifications'], true); - }); - case MUTES_TOGGLE_HIDE_NOTIFICATIONS: - return state.updateIn(['new', 'notifications'], (old) => !old); - case MUTES_CHANGE_DURATION: - return state.setIn(['new', 'duration'], Number(action.duration)); - default: - return state; - } -} diff --git a/app/javascript/material-icons/400-24px/badge-fill.svg b/app/javascript/material-icons/400-24px/badge-fill.svg new file mode 100644 index 0000000000..2f7175b7f1 --- /dev/null +++ b/app/javascript/material-icons/400-24px/badge-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/badge.svg b/app/javascript/material-icons/400-24px/badge.svg new file mode 100644 index 0000000000..d413363a4c --- /dev/null +++ b/app/javascript/material-icons/400-24px/badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/domain_disabled-fill.svg b/app/javascript/material-icons/400-24px/domain_disabled-fill.svg new file mode 100644 index 0000000000..2f16593d38 --- /dev/null +++ b/app/javascript/material-icons/400-24px/domain_disabled-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/domain_disabled.svg b/app/javascript/material-icons/400-24px/domain_disabled.svg new file mode 100644 index 0000000000..2f16593d38 --- /dev/null +++ b/app/javascript/material-icons/400-24px/domain_disabled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/globe-fill.svg b/app/javascript/material-icons/400-24px/globe-fill.svg new file mode 100644 index 0000000000..c931f53c74 --- /dev/null +++ b/app/javascript/material-icons/400-24px/globe-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/globe.svg b/app/javascript/material-icons/400-24px/globe.svg new file mode 100644 index 0000000000..c931f53c74 --- /dev/null +++ b/app/javascript/material-icons/400-24px/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/history-fill.svg b/app/javascript/material-icons/400-24px/history-fill.svg new file mode 100644 index 0000000000..2d8124b474 --- /dev/null +++ b/app/javascript/material-icons/400-24px/history-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/history.svg b/app/javascript/material-icons/400-24px/history.svg new file mode 100644 index 0000000000..2d8124b474 --- /dev/null +++ b/app/javascript/material-icons/400-24px/history.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_remove-fill.svg b/app/javascript/material-icons/400-24px/person_remove-fill.svg new file mode 100644 index 0000000000..239c7a49dc --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_remove-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/person_remove.svg b/app/javascript/material-icons/400-24px/person_remove.svg new file mode 100644 index 0000000000..725da3649b --- /dev/null +++ b/app/javascript/material-icons/400-24px/person_remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/star-fill.svg b/app/javascript/material-icons/400-24px/star-fill.svg index cb2231e634..84e8230ab7 100644 --- a/app/javascript/material-icons/400-24px/star-fill.svg +++ b/app/javascript/material-icons/400-24px/star-fill.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/star.svg b/app/javascript/material-icons/400-24px/star.svg index 1736e085d0..6a72ecc226 100644 --- a/app/javascript/material-icons/400-24px/star.svg +++ b/app/javascript/material-icons/400-24px/star.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx deleted file mode 100644 index be66683986..0000000000 --- a/app/javascript/packs/public.jsx +++ /dev/null @@ -1,229 +0,0 @@ -import './public-path'; - -import { createRoot } from 'react-dom/client'; - -import { IntlMessageFormat } from 'intl-messageformat'; -import { defineMessages } from 'react-intl'; - -import Rails from '@rails/ujs'; -import axios from 'axios'; -import { throttle } from 'lodash'; - -import { start } from '../mastodon/common'; -import { timeAgoString } from '../mastodon/components/relative_timestamp'; -import emojify from '../mastodon/features/emoji/emoji'; -import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions'; -import { loadLocale, getLocale } from '../mastodon/locales'; -import { loadPolyfills } from '../mastodon/polyfills'; -import ready from '../mastodon/ready'; - -import 'cocoon-js-vanilla'; - -start(); - -const messages = defineMessages({ - usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' }, - passwordExceedsLength: { id: 'password_confirmation.exceeds_maxlength', defaultMessage: 'Password confirmation exceeds the maximum password length' }, - passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' }, -}); - -function loaded() { - const { messages: localeData } = getLocale(); - - const locale = document.documentElement.lang; - - const dateTimeFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - }); - - const dateFormat = new Intl.DateTimeFormat(locale, { - year: 'numeric', - month: 'short', - day: 'numeric', - timeFormat: false, - }); - - const timeFormat = new Intl.DateTimeFormat(locale, { - timeStyle: 'short', - }); - - const formatMessage = ({ id, defaultMessage }, values) => { - const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale); - return messageFormat.format(values); - }; - - document.querySelectorAll('.emojify').forEach((content) => { - content.innerHTML = emojify(content.innerHTML); - }); - - document.querySelectorAll('time.formatted').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - const formattedDate = dateTimeFormat.format(datetime); - - content.title = formattedDate; - content.textContent = formattedDate; - }); - - const isToday = date => { - const today = new Date(); - - return date.getDate() === today.getDate() && - date.getMonth() === today.getMonth() && - date.getFullYear() === today.getFullYear(); - }; - const todayFormat = new IntlMessageFormat(localeData['relative_format.today'] || 'Today at {time}', locale); - - document.querySelectorAll('time.relative-formatted').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - - let formattedContent; - - if (isToday(datetime)) { - const formattedTime = timeFormat.format(datetime); - - formattedContent = todayFormat.format({ time: formattedTime }); - } else { - formattedContent = dateFormat.format(datetime); - } - - content.title = formattedContent; - content.textContent = formattedContent; - }); - - document.querySelectorAll('time.time-ago').forEach((content) => { - const datetime = new Date(content.getAttribute('datetime')); - const now = new Date(); - - const timeGiven = content.getAttribute('datetime').includes('T'); - content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime); - content.textContent = timeAgoString({ - formatMessage, - formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date), - }, datetime, now, now.getFullYear(), timeGiven); - }); - - const reactComponents = document.querySelectorAll('[data-component]'); - - if (reactComponents.length > 0) { - import(/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container') - .then(({ default: MediaContainer }) => { - reactComponents.forEach((component) => { - Array.from(component.children).forEach((child) => { - component.removeChild(child); - }); - }); - - const content = document.createElement('div'); - - const root = createRoot(content); - root.render(); - document.body.appendChild(content); - }) - .catch(error => { - console.error(error); - }); - } - - Rails.delegate(document, '#user_account_attributes_username', 'input', throttle(({ target }) => { - if (target.value && target.value.length > 0) { - axios.get('/api/v1/accounts/lookup', { params: { acct: target.value } }).then(() => { - target.setCustomValidity(formatMessage(messages.usernameTaken)); - }).catch(() => { - target.setCustomValidity(''); - }); - } else { - target.setCustomValidity(''); - } - }, 500, { leading: false, trailing: true })); - - Rails.delegate(document, '#user_password,#user_password_confirmation', 'input', () => { - const password = document.getElementById('user_password'); - const confirmation = document.getElementById('user_password_confirmation'); - if (!confirmation) return; - - if (confirmation.value && confirmation.value.length > password.maxLength) { - confirmation.setCustomValidity(formatMessage(messages.passwordExceedsLength)); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity(formatMessage(messages.passwordDoesNotMatch)); - } else { - confirmation.setCustomValidity(''); - } - }); - - Rails.delegate(document, '.status__content__spoiler-link', 'click', function() { - const statusEl = this.parentNode.parentNode; - - if (statusEl.dataset.spoiler === 'expanded') { - statusEl.dataset.spoiler = 'folded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format(); - } else { - statusEl.dataset.spoiler = 'expanded'; - this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format(); - } - - return false; - }); - - document.querySelectorAll('.status__content__spoiler-link').forEach((spoilerLink) => { - const statusEl = spoilerLink.parentNode.parentNode; - const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more'); - spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); - }); -} - -const toggleSidebar = () => { - const sidebar = document.querySelector('.sidebar ul'); - const toggleButton = document.querySelector('.sidebar__toggle__icon'); - - if (sidebar.classList.contains('visible')) { - document.body.style.overflow = null; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - document.body.style.overflow = 'hidden'; - toggleButton.setAttribute('aria-expanded', 'true'); - } - - toggleButton.classList.toggle('active'); - sidebar.classList.toggle('visible'); -}; - -Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { - toggleSidebar(); -}); - -Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', e => { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - toggleSidebar(); - } -}); - -Rails.delegate(document, '.custom-emoji', 'mouseover', ({ target }) => target.src = target.getAttribute('data-original')); -Rails.delegate(document, '.custom-emoji', 'mouseout', ({ target }) => target.src = target.getAttribute('data-static')); - -// Empty the honeypot fields in JS in case something like an extension -// automatically filled them. -Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { - ['user_website', 'user_confirm_password', 'registration_user_website', 'registration_user_confirm_password'].forEach(id => { - const field = document.getElementById(id); - if (field) { - field.value = ''; - } - }); -}); - -function main() { - ready(loaded); -} - -loadPolyfills() - .then(loadLocale) - .then(main) - .then(loadKeyboardExtensions) - .catch(error => { - console.error(error); - }); diff --git a/app/javascript/packs/public.tsx b/app/javascript/packs/public.tsx new file mode 100644 index 0000000000..17befbd224 --- /dev/null +++ b/app/javascript/packs/public.tsx @@ -0,0 +1,359 @@ +import { createRoot } from 'react-dom/client'; + +import './public-path'; + +import { IntlMessageFormat } from 'intl-messageformat'; +import type { MessageDescriptor, PrimitiveType } from 'react-intl'; +import { defineMessages } from 'react-intl'; + +import Rails from '@rails/ujs'; +import axios from 'axios'; +import { throttle } from 'lodash'; + +import { start } from '../mastodon/common'; +import { timeAgoString } from '../mastodon/components/relative_timestamp'; +import emojify from '../mastodon/features/emoji/emoji'; +import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions'; +import { loadLocale, getLocale } from '../mastodon/locales'; +import { loadPolyfills } from '../mastodon/polyfills'; +import ready from '../mastodon/ready'; + +import 'cocoon-js-vanilla'; + +start(); + +const messages = defineMessages({ + usernameTaken: { + id: 'username.taken', + defaultMessage: 'That username is taken. Try another', + }, + passwordExceedsLength: { + id: 'password_confirmation.exceeds_maxlength', + defaultMessage: 'Password confirmation exceeds the maximum password length', + }, + passwordDoesNotMatch: { + id: 'password_confirmation.mismatching', + defaultMessage: 'Password confirmation does not match', + }, +}); + +function loaded() { + const { messages: localeData } = getLocale(); + + const locale = document.documentElement.lang; + + const dateTimeFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }); + + const dateFormat = new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + day: 'numeric', + }); + + const timeFormat = new Intl.DateTimeFormat(locale, { + timeStyle: 'short', + }); + + const formatMessage = ( + { id, defaultMessage }: MessageDescriptor, + values?: Record, + ) => { + let message: string | undefined = undefined; + + if (id) message = localeData[id]; + + if (!message) message = defaultMessage as string; + + const messageFormat = new IntlMessageFormat(message, locale); + return messageFormat.format(values) as string; + }; + + document.querySelectorAll('.emojify').forEach((content) => { + content.innerHTML = emojify(content.innerHTML); + }); + + document + .querySelectorAll('time.formatted') + .forEach((content) => { + const datetime = new Date(content.dateTime); + const formattedDate = dateTimeFormat.format(datetime); + + content.title = formattedDate; + content.textContent = formattedDate; + }); + + const isToday = (date: Date) => { + const today = new Date(); + + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ); + }; + const todayFormat = new IntlMessageFormat( + localeData['relative_format.today'] || 'Today at {time}', + locale, + ); + + document + .querySelectorAll('time.relative-formatted') + .forEach((content) => { + const datetime = new Date(content.dateTime); + + let formattedContent: string; + + if (isToday(datetime)) { + const formattedTime = timeFormat.format(datetime); + + formattedContent = todayFormat.format({ + time: formattedTime, + }) as string; + } else { + formattedContent = dateFormat.format(datetime); + } + + content.title = formattedContent; + content.textContent = formattedContent; + }); + + document + .querySelectorAll('time.time-ago') + .forEach((content) => { + const datetime = new Date(content.dateTime); + const now = new Date(); + + const timeGiven = content.dateTime.includes('T'); + content.title = timeGiven + ? dateTimeFormat.format(datetime) + : dateFormat.format(datetime); + content.textContent = timeAgoString( + { + formatMessage, + formatDate: (date: Date, options) => + new Intl.DateTimeFormat(locale, options).format(date), + }, + datetime, + now.getTime(), + now.getFullYear(), + timeGiven, + ); + }); + + const reactComponents = document.querySelectorAll('[data-component]'); + + if (reactComponents.length > 0) { + import( + /* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container' + ) + .then(({ default: MediaContainer }) => { + reactComponents.forEach((component) => { + Array.from(component.children).forEach((child) => { + component.removeChild(child); + }); + }); + + const content = document.createElement('div'); + + const root = createRoot(content); + root.render( + , + ); + document.body.appendChild(content); + + return true; + }) + .catch((error) => { + console.error(error); + }); + } + + Rails.delegate( + document, + 'input#user_account_attributes_username', + 'input', + throttle( + ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; + + if (target.value && target.value.length > 0) { + axios + .get('/api/v1/accounts/lookup', { params: { acct: target.value } }) + .then(() => { + target.setCustomValidity(formatMessage(messages.usernameTaken)); + return true; + }) + .catch(() => { + target.setCustomValidity(''); + }); + } else { + target.setCustomValidity(''); + } + }, + 500, + { leading: false, trailing: true }, + ), + ); + + Rails.delegate( + document, + '#user_password,#user_password_confirmation', + 'input', + () => { + const password = document.querySelector( + 'input#user_password', + ); + const confirmation = document.querySelector( + 'input#user_password_confirmation', + ); + if (!confirmation || !password) return; + + if ( + confirmation.value && + confirmation.value.length > password.maxLength + ) { + confirmation.setCustomValidity( + formatMessage(messages.passwordExceedsLength), + ); + } else if (password.value && password.value !== confirmation.value) { + confirmation.setCustomValidity( + formatMessage(messages.passwordDoesNotMatch), + ); + } else { + confirmation.setCustomValidity(''); + } + }, + ); + + Rails.delegate( + document, + 'button.status__content__spoiler-link', + 'click', + function () { + if (!(this instanceof HTMLButtonElement)) return; + + const statusEl = this.parentNode?.parentNode; + + if ( + !( + statusEl instanceof HTMLDivElement && + statusEl.classList.contains('.status__content') + ) + ) + return; + + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; + this.textContent = new IntlMessageFormat( + localeData['status.show_more'] || 'Show more', + locale, + ).format() as string; + } else { + statusEl.dataset.spoiler = 'expanded'; + this.textContent = new IntlMessageFormat( + localeData['status.show_less'] || 'Show less', + locale, + ).format() as string; + } + }, + ); + + document + .querySelectorAll('button.status__content__spoiler-link') + .forEach((spoilerLink) => { + const statusEl = spoilerLink.parentNode?.parentNode; + + if ( + !( + statusEl instanceof HTMLDivElement && + statusEl.classList.contains('.status__content') + ) + ) + return; + + const message = + statusEl.dataset.spoiler === 'expanded' + ? localeData['status.show_less'] || 'Show less' + : localeData['status.show_more'] || 'Show more'; + spoilerLink.textContent = new IntlMessageFormat( + message, + locale, + ).format() as string; + }); +} + +const toggleSidebar = () => { + const sidebar = document.querySelector('.sidebar ul'); + const toggleButton = document.querySelector( + 'a.sidebar__toggle__icon', + ); + + if (!sidebar || !toggleButton) return; + + if (sidebar.classList.contains('visible')) { + document.body.style.overflow = ''; + toggleButton.setAttribute('aria-expanded', 'false'); + } else { + document.body.style.overflow = 'hidden'; + toggleButton.setAttribute('aria-expanded', 'true'); + } + + toggleButton.classList.toggle('active'); + sidebar.classList.toggle('visible'); +}; + +Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { + toggleSidebar(); +}); + +Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + toggleSidebar(); + } +}); + +Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => { + if (target instanceof HTMLImageElement && target.dataset.original) + target.src = target.dataset.original; +}); +Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => { + if (target instanceof HTMLImageElement && target.dataset.static) + target.src = target.dataset.static; +}); + +// Empty the honeypot fields in JS in case something like an extension +// automatically filled them. +Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { + [ + 'user_website', + 'user_confirm_password', + 'registration_user_website', + 'registration_user_confirm_password', + ].forEach((id) => { + const field = document.querySelector(`input#${id}`); + if (field) { + field.value = ''; + } + }); +}); + +function main() { + ready(loaded).catch((error) => { + console.error(error); + }); +} + +loadPolyfills() + .then(loadLocale) + .then(main) + .then(loadKeyboardExtensions) + .catch((error) => { + console.error(error); + }); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 864b5c971f..ca87cbdaac 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -106,17 +106,17 @@ } &.button-secondary { - color: $ui-button-secondary-color; + color: $highlight-text-color; background: transparent; padding: 6px 17px; - border: 1px solid $ui-button-secondary-border-color; + border: 1px solid $highlight-text-color; &:active, &:focus, &:hover { - border-color: $ui-button-secondary-focus-background-color; - color: $ui-button-secondary-focus-color; - background-color: $ui-button-secondary-focus-background-color; + border-color: lighten($highlight-text-color, 4%); + color: lighten($highlight-text-color, 4%); + background-color: transparent; text-decoration: none; } @@ -1792,6 +1792,118 @@ body > [data-popper-placement] { } } + &__domain-pill { + display: inline-flex; + background: rgba($highlight-text-color, 0.2); + border-radius: 4px; + border: 0; + color: $highlight-text-color; + font-weight: 500; + font-size: 12px; + line-height: 16px; + padding: 4px 8px; + + &.active { + color: $white; + background: $ui-highlight-color; + } + + &__popout { + background: var(--dropdown-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--dropdown-border-color); + box-shadow: var(--dropdown-shadow); + max-width: 320px; + padding: 16px; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 24px; + font-size: 14px; + line-height: 20px; + color: $darker-text-color; + + .link-button { + display: inline; + font-size: inherit; + line-height: inherit; + } + + &__header { + display: flex; + align-items: center; + gap: 12px; + + &__icon { + width: 40px; + height: 40px; + background: $ui-highlight-color; + color: $white; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + flex-shrink: 0; + } + + h3 { + font-size: 17px; + line-height: 22px; + color: $primary-text-color; + } + } + + &__handle { + border: 2px dashed $highlight-text-color; + background: rgba($highlight-text-color, 0.1); + padding: 12px 8px; + color: $highlight-text-color; + border-radius: 4px; + + &__label { + font-size: 11px; + line-height: 16px; + font-weight: 500; + } + + &__handle { + user-select: all; + } + } + + &__parts { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 12px; + line-height: 16px; + + & > div { + display: flex; + align-items: flex-start; + gap: 12px; + } + + &__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: $highlight-text-color; + } + + h6 { + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: $primary-text-color; + } + } + } + } + &__note { font-size: 14px; font-weight: 400; @@ -5368,6 +5480,10 @@ a.status-card { pointer-events: auto; user-select: text; display: flex; + + @media screen and (max-width: $no-gap-breakpoint) { + margin-top: auto; + } } .video-modal .video-player { @@ -5699,6 +5815,154 @@ a.status-card { margin-inline-start: 10px; } +.safety-action-modal { + width: 600px; + flex-direction: column; + + &__top, + &__bottom { + display: flex; + gap: 8px; + padding: 24px; + flex-direction: column; + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + } + + &__top { + border-radius: 16px 16px 0 0; + border-bottom: 0; + gap: 16px; + } + + &__bottom { + border-radius: 0 0 16px 16px; + border-top: 0; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 0; + border-bottom: 0; + padding-bottom: 32px; + } + } + + &__header { + display: flex; + gap: 16px; + align-items: center; + font-size: 14px; + line-height: 20px; + color: $darker-text-color; + + &__icon { + border-radius: 64px; + background: $ui-highlight-color; + color: $white; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + + .icon { + width: 24px; + height: 24px; + } + } + + h1 { + font-size: 22px; + line-height: 28px; + color: $primary-text-color; + } + } + + &__bullet-points { + display: flex; + flex-direction: column; + gap: 8px; + font-size: 16px; + line-height: 24px; + + & > div { + display: flex; + gap: 16px; + align-items: center; + } + + &__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + .icon { + width: 24px; + height: 24px; + } + } + } + + &__field-group { + display: flex; + flex-direction: column; + + label { + display: flex; + gap: 16px; + align-items: center; + font-size: 16px; + line-height: 24px; + height: 32px; + padding: 0 12px; + } + } + + &__caveats { + font-size: 14px; + padding: 0 12px; + + strong { + font-weight: 500; + } + } + + &__bottom { + padding-top: 0; + + &__collapsible { + display: none; + flex-direction: column; + gap: 16px; + } + + &.active { + background: var(--modal-background-variant-color); + padding-top: 24px; + + .safety-action-modal__bottom__collapsible { + display: flex; + } + } + } + + &__actions { + display: flex; + align-items: center; + gap: 8px; + justify-content: flex-end; + + .link-button { + padding: 10px 12px; + font-weight: 600; + } + } +} + .boost-modal, .confirmation-modal, .report-modal, @@ -7001,7 +7265,8 @@ a.status-card { display: flex; } -.radio-button { +.radio-button, +.check-box { font-size: 14px; position: relative; display: inline-flex; @@ -7020,17 +7285,19 @@ a.status-card { } &__input { - display: block; + display: flex; + align-items: center; + justify-content: center; position: relative; border: 2px solid $secondary-text-color; box-sizing: border-box; - width: 18px; - height: 18px; + width: 20px; + height: 20px; flex: 0 0 auto; border-radius: 50%; &.checked { - border-color: $secondary-text-color; + border-color: $ui-highlight-color; &::before { position: absolute; @@ -7039,9 +7306,31 @@ a.status-card { content: ''; display: block; border-radius: 50%; - width: 10px; - height: 10px; - background: $secondary-text-color; + width: 12px; + height: 12px; + background: $ui-highlight-color; + } + } + + .icon { + width: 18px; + height: 18px; + } + } +} + +.check-box { + &__input { + width: 18px; + height: 18px; + border-radius: 2px; + + &.checked { + background: $ui-highlight-color; + color: $white; + + &::before { + display: none; } } } @@ -7544,14 +7833,17 @@ noscript { font-size: 17px; line-height: 22px; color: $primary-text-color; - font-weight: 700; + font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; small { - display: block; - font-size: 15px; + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + line-height: 20px; color: $darker-text-color; font-weight: 400; overflow: hidden; @@ -7562,10 +7854,8 @@ noscript { } .icon-lock { - height: 16px; - width: 16px; - position: relative; - top: 3px; + height: 18px; + width: 18px; } } } @@ -8519,22 +8809,36 @@ noscript { } } +.safety-action-modal, .interaction-modal { max-width: 90vw; width: 600px; - background: var(--modal-background-color); - border: 1px solid var(--modal-border-color); - border-radius: 8px; +} + +.interaction-modal { overflow: visible; position: relative; display: block; - padding: 40px; + border-radius: 16px; + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + padding: 24px; + + @media screen and (max-width: $no-gap-breakpoint) { + border-radius: 16px 16px 0 0; + border-bottom: 0; + padding-bottom: 32px; + } h3 { font-size: 22px; line-height: 33px; font-weight: 700; - text-align: center; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; } p { @@ -8555,7 +8859,9 @@ noscript { &__icon { color: $highlight-text-color; - margin: 0 5px; + display: flex; + align-items: center; + justify-content: center; } &__lead { @@ -8588,6 +8894,7 @@ noscript { border: 0; padding: 15px - 4px 15px - 6px; flex: 1 1 auto; + min-width: 0; &::placeholder { color: lighten($darker-text-color, 4%); diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index 2b2a295791..58b9dd9b61 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -46,10 +46,10 @@ $ui-button-focus-background-color: $blurple-600 !default; $ui-button-focus-outline-color: $blurple-400 !default; $ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default; -$ui-button-secondary-color: $grey-100 !default; -$ui-button-secondary-border-color: $grey-100 !default; -$ui-button-secondary-focus-background-color: $grey-600 !default; -$ui-button-secondary-focus-color: $white !default; +$ui-button-secondary-color: $blurple-500 !default; +$ui-button-secondary-border-color: $blurple-500 !default; +$ui-button-secondary-focus-border-color: $blurple-300 !default; +$ui-button-secondary-focus-color: $blurple-300 !default; $ui-button-tertiary-color: $blurple-300 !default; $ui-button-tertiary-border-color: $blurple-300 !default; @@ -98,7 +98,8 @@ $font-monospace: 'mastodon-font-monospace' !default; --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)}; --dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)}, 0 8px 10px -6px #{rgba($base-shadow-color, 0.25)}; - --modal-background-color: #{darken($ui-base-color, 4%)}; + --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)}; + --modal-background-variant-color: #{rgba($ui-base-color, 0.7)}; --modal-border-color: #{lighten($ui-base-color, 4%)}; --background-border-color: #{lighten($ui-base-color, 4%)}; --background-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb index 9160ef22a6..d0e675f07d 100644 --- a/app/lib/activity_tracker.rb +++ b/app/lib/activity_tracker.rb @@ -24,7 +24,7 @@ class ActivityTracker end def get(start_at, end_at = Time.now.utc) - (start_at.to_date...end_at.to_date).map do |date| + (start_at.to_date..end_at.to_date).map do |date| key = key_at(date.to_time(:utc)) value = case @type diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 8dbbd06f19..518c1792b8 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -246,10 +246,6 @@ module Account::Interactions status_pins.exists?(status: status) end - def endorsed?(account) - account_pins.exists?(target_account: account) - end - def status_matches_filters(status) active_filters = CustomFilter.cached_filters_for(id) CustomFilter.apply_cached_filters(active_filters, status) diff --git a/app/views/admin/account_actions/new.html.haml b/app/views/admin/account_actions/new.html.haml index 4cb4401c70..bce1c31760 100644 --- a/app/views/admin/account_actions/new.html.haml +++ b/app/views/admin/account_actions/new.html.haml @@ -2,29 +2,48 @@ = t('admin.account_actions.title', acct: @account.pretty_acct) = simple_form_for @account_action, url: admin_account_action_path(@account.id) do |f| - = f.input :report_id, as: :hidden + = f.input :report_id, + as: :hidden .fields-group - = f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { account_action_type_label(type) }, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct) + = f.input :type, + as: :radio_buttons, + collection: Admin::AccountAction.types_for_account(@account), + hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct), + include_blank: false, + label_method: ->(type) { account_action_type_label(type) }, + wrapper: :with_block_label - if @account.local? %hr.spacer/ .fields-group - = f.input :send_email_notification, as: :boolean, wrapper: :with_label + = f.input :send_email_notification, + as: :boolean, + wrapper: :with_label - if params[:report_id].present? .fields-group - = f.input :include_statuses, as: :boolean, wrapper: :with_label + = f.input :include_statuses, + as: :boolean, + wrapper: :with_label %hr.spacer/ - unless @warning_presets.empty? .fields-group - = f.input :warning_preset_id, collection: @warning_presets, label_method: ->(warning_preset) { [warning_preset.title.presence, truncate(warning_preset.text)].compact.join(' - ') }, wrapper: :with_block_label + = f.input :warning_preset_id, + collection: @warning_presets, + label_method: ->(warning_preset) { [warning_preset.title.presence, truncate(warning_preset.text)].compact.join(' - ') }, + wrapper: :with_block_label .fields-group - = f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path) + = f.input :text, + as: :text, + hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path), + wrapper: :with_block_label .actions - = f.button :button, t('admin.account_actions.action'), type: :submit + = f.button :button, + t('admin.account_actions.action'), + type: :submit diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 8354441895..0ca457f39e 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -6,19 +6,26 @@ .filter-subset.filter-subset--with-select %strong= t('admin.accounts.location.title') .input.select.optional - = select_tag :origin, options_for_select([[t('admin.accounts.location.local'), 'local'], [t('admin.accounts.location.remote'), 'remote']], params[:origin]), prompt: I18n.t('generic.all') + = select_tag :origin, + options_for_select([[t('admin.accounts.location.local'), 'local'], [t('admin.accounts.location.remote'), 'remote']], params[:origin]), + prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.moderation.title') .input.select.optional - = select_tag :status, options_for_select(admin_accounts_moderation_options, params[:status]), prompt: I18n.t('generic.all') + = select_tag :status, + options_for_select(admin_accounts_moderation_options, params[:status]), + prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.role') .input.select.optional - = select_tag :role_ids, options_from_collection_for_select(UserRole.assignable, :id, :name, params[:role_ids]), prompt: I18n.t('admin.accounts.moderation.all') + = select_tag :role_ids, + options_from_collection_for_select(UserRole.assignable, :id, :name, params[:role_ids]), + prompt: I18n.t('admin.accounts.moderation.all') .filter-subset.filter-subset--with-select %strong= t 'generic.order_by' .input.select - = select_tag :order, options_for_select([[t('relationships.most_recent'), 'recent'], [t('relationships.last_active'), 'active']], params[:order]) + = select_tag :order, + options_for_select([[t('relationships.most_recent'), 'recent'], [t('relationships.last_active'), 'active']], params[:order]) .fields-group - %i(username by_domain display_name email ip).each do |key| @@ -46,11 +53,23 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if @accounts.any?(&:user_pending?) - = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve, + type: :submit - = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject, + type: :submit - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), name: :suspend, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :suspend, + type: :submit - if @accounts.total_count > @accounts.size .batch-table__select-all .not-selected.active diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml index 150d98272f..23c568a885 100644 --- a/app/views/admin/announcements/edit.html.haml +++ b/app/views/admin/announcements/edit.html.haml @@ -5,18 +5,33 @@ = render 'shared/error_messages', object: @announcement .fields-group - = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } - = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } + = f.input :starts_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label + = f.input :ends_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label .fields-group - = f.input :all_day, as: :boolean, wrapper: :with_label + = f.input :all_day, + as: :boolean, + wrapper: :with_label .fields-group - = f.input :text, wrapper: :with_block_label + = f.input :text, + wrapper: :with_block_label - unless @announcement.published? .fields-group - = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } + = f.input :scheduled_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml index 0123632ff4..a681ed789e 100644 --- a/app/views/admin/announcements/new.html.haml +++ b/app/views/admin/announcements/new.html.haml @@ -5,17 +5,34 @@ = render 'shared/error_messages', object: @announcement .fields-group - = f.input :starts_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } - = f.input :ends_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } + = f.input :starts_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label + = f.input :ends_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label .fields-group - = f.input :all_day, as: :boolean, wrapper: :with_label + = f.input :all_day, + as: :boolean, + wrapper: :with_label .fields-group - = f.input :text, wrapper: :with_block_label + = f.input :text, + wrapper: :with_block_label .fields-group - = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label, html5: true, input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder } + = f.input :scheduled_at, + html5: true, + include_blank: true, + input_html: { pattern: datetime_pattern, placeholder: datetime_placeholder }, + wrapper: :with_block_label .actions - = f.button :button, t('.create'), type: :submit + = f.button :button, + t('.create'), + type: :submit diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index 8b4e93ac35..bea6a7cd21 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -68,12 +68,19 @@ .fields-group.fields-row__column.fields-row__column-6 .input.select.optional .label_input - = f.select :category_id, options_from_collection_for_select(CustomEmojiCategory.all, 'id', 'name'), prompt: t('admin.custom_emojis.assign_category'), class: 'select optional', 'aria-label': t('admin.custom_emojis.assign_category') + = f.select :category_id, + options_from_collection_for_select(CustomEmojiCategory.all, 'id', 'name'), + 'aria-label': t('admin.custom_emojis.assign_category'), + class: 'select optional', + prompt: t('admin.custom_emojis.assign_category') .fields-group.fields-row__column.fields-row__column-6 .input.string.optional .label_input - = f.text_field :category_name, class: 'string optional', placeholder: t('admin.custom_emojis.create_new_category'), 'aria-label': t('admin.custom_emojis.create_new_category') + = f.text_field :category_name, + 'aria-label': t('admin.custom_emojis.create_new_category'), + class: 'string optional', + placeholder: t('admin.custom_emojis.create_new_category') .batch-table__body - if @custom_emojis.empty? diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/admin/custom_emojis/new.html.haml index 69b0458ede..11ea037351 100644 --- a/app/views/admin/custom_emojis/new.html.haml +++ b/app/views/admin/custom_emojis/new.html.haml @@ -5,9 +5,17 @@ = render 'shared/error_messages', object: @custom_emoji .fields-group - = f.input :shortcode, wrapper: :with_label, label: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint') + = f.input :shortcode, + wrapper: :with_label, + label: t('admin.custom_emojis.shortcode'), + hint: t('admin.custom_emojis.shortcode_hint') .fields-group - = f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(',') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LOCAL_LIMIT)) + = f.input :image, + wrapper: :with_label, + input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(',') }, + hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LOCAL_LIMIT)) .actions - = f.button :button, t('admin.custom_emojis.upload'), type: :submit + = f.button :button, + t('admin.custom_emojis.upload'), + type: :submit diff --git a/app/views/admin/domain_blocks/edit.html.haml b/app/views/admin/domain_blocks/edit.html.haml index ae76b6777a..7c0a9823a0 100644 --- a/app/views/admin/domain_blocks/edit.html.haml +++ b/app/views/admin/domain_blocks/edit.html.haml @@ -6,25 +6,58 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('admin.domain_blocks.new.hint'), required: true, readonly: true, disabled: true + = f.input :domain, + disabled: true, + hint: t('admin.domain_blocks.new.hint'), + label: t('admin.domain_blocks.domain'), + readonly: true, + required: true, + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html') + = f.input :severity, + collection: DomainBlock.severities.keys, + hint: t('admin.domain_blocks.new.severity.desc_html'), + include_blank: false, + label_method: ->(type) { t("admin.domain_blocks.new.severity.#{type}") }, + wrapper: :with_label .fields-group - = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint') + = f.input :reject_media, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_media_hint'), + label: I18n.t('admin.domain_blocks.reject_media'), + wrapper: :with_label .fields-group - = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') + = f.input :reject_reports, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_reports_hint'), + label: I18n.t('admin.domain_blocks.reject_reports'), + wrapper: :with_label .fields-group - = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint') + = f.input :obfuscate, + as: :boolean, + hint: I18n.t('admin.domain_blocks.obfuscate_hint'), + label: I18n.t('admin.domain_blocks.obfuscate'), + wrapper: :with_label .field-group - = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), as: :string + = f.input :private_comment, + as: :string, + hint: t('admin.domain_blocks.private_comment_hint'), + label: I18n.t('admin.domain_blocks.private_comment'), + wrapper: :with_label .field-group - = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), as: :string + = f.input :public_comment, + as: :string, + hint: t('admin.domain_blocks.public_comment_hint'), + label: I18n.t('admin.domain_blocks.public_comment'), + wrapper: :with_label .actions - = f.button :button, t('generic.save_changes'), type: :submit + = f.button :button, + t('generic.save_changes'), + type: :submit diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml index 86e519f451..6a9855529f 100644 --- a/app/views/admin/domain_blocks/new.html.haml +++ b/app/views/admin/domain_blocks/new.html.haml @@ -6,25 +6,56 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('.hint'), required: true + = f.input :domain, + hint: t('.hint'), + label: t('admin.domain_blocks.domain'), + required: true, + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: ->(type) { t(".severity.#{type}") }, hint: t('.severity.desc_html') + = f.input :severity, + collection: DomainBlock.severities.keys, + hint: t('.severity.desc_html'), + include_blank: false, + label_method: ->(type) { t(".severity.#{type}") }, + wrapper: :with_label .fields-group - = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint') + = f.input :reject_media, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_media_hint'), + label: I18n.t('admin.domain_blocks.reject_media'), + wrapper: :with_label .fields-group - = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') + = f.input :reject_reports, + as: :boolean, + hint: I18n.t('admin.domain_blocks.reject_reports_hint'), + label: I18n.t('admin.domain_blocks.reject_reports'), + wrapper: :with_label .fields-group - = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint') + = f.input :obfuscate, + as: :boolean, + hint: I18n.t('admin.domain_blocks.obfuscate_hint'), + label: I18n.t('admin.domain_blocks.obfuscate'), + wrapper: :with_label .field-group - = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), as: :string + = f.input :private_comment, + as: :string, + hint: t('admin.domain_blocks.private_comment_hint'), + label: I18n.t('admin.domain_blocks.private_comment'), + wrapper: :with_label .field-group - = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), as: :string + = f.input :public_comment, + as: :string, + hint: t('admin.domain_blocks.public_comment_hint'), + label: I18n.t('admin.domain_blocks.public_comment'), + wrapper: :with_label .actions - = f.button :button, t('.create'), type: :submit + = f.button :button, + t('.create'), + type: :submit diff --git a/app/views/admin/email_domain_blocks/index.html.haml b/app/views/admin/email_domain_blocks/index.html.haml index 9f16e0d5c3..59036f899f 100644 --- a/app/views/admin/email_domain_blocks/index.html.haml +++ b/app/views/admin/email_domain_blocks/index.html.haml @@ -12,7 +12,11 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('times'), t('admin.email_domain_blocks.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('times'), t('admin.email_domain_blocks.delete')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :delete, + type: :submit .batch-table__body - if @email_domain_blocks.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/email_domain_blocks/new.html.haml b/app/views/admin/email_domain_blocks/new.html.haml index 3d31487733..dd4b83ee3f 100644 --- a/app/views/admin/email_domain_blocks/new.html.haml +++ b/app/views/admin/email_domain_blocks/new.html.haml @@ -5,10 +5,16 @@ = render 'shared/error_messages', object: @email_domain_block .fields-group - = f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain'), input_html: { readonly: defined?(@resolved_records) } + = f.input :domain, + input_html: { readonly: defined?(@resolved_records) }, + label: t('admin.email_domain_blocks.domain'), + wrapper: :with_block_label .fields-group - = f.input :allow_with_approval, wrapper: :with_label, hint: false, label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval') + = f.input :allow_with_approval, + hint: false, + label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval'), + wrapper: :with_label - if defined?(@resolved_records) %p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html') @@ -22,7 +28,11 @@ - @resolved_records.each do |record| .batch-table__row %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox - = f.input_field :other_domains, as: :boolean, checked_value: record.exchange.to_s, include_hidden: false, multiple: true + = f.input_field :other_domains, + as: :boolean, + checked_value: record.exchange.to_s, + include_hidden: false, + multiple: true .batch-table__row__content.pending-account .pending-account__header %samp= record.exchange.to_s diff --git a/app/views/admin/export_domain_allows/new.html.haml b/app/views/admin/export_domain_allows/new.html.haml index dc0cf8c52a..66c6aa8d8a 100644 --- a/app/views/admin/export_domain_allows/new.html.haml +++ b/app/views/admin/export_domain_allows/new.html.haml @@ -4,7 +4,10 @@ = simple_form_for @import, url: import_admin_export_domain_allows_path, html: { multipart: true } do |f| .fields-row .fields-group.fields-row__column.fields-row__column-6 - = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data'), as: :file + = f.input :data, + as: :file, + hint: t('simple_form.hints.imports.data'), + wrapper: :with_block_label .actions = f.button :button, t('imports.upload'), type: :submit diff --git a/app/views/admin/export_domain_blocks/import.html.haml b/app/views/admin/export_domain_blocks/import.html.haml index 01add232d1..48016a9abe 100644 --- a/app/views/admin/export_domain_blocks/import.html.haml +++ b/app/views/admin/export_domain_blocks/import.html.haml @@ -12,7 +12,11 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('copy'), t('admin.domain_blocks.import')]), name: :save, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('copy'), t('admin.domain_blocks.import')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :save, + type: :submit .batch-table__body - if @domain_blocks.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/export_domain_blocks/new.html.haml b/app/views/admin/export_domain_blocks/new.html.haml index 0291aeed77..9c960d47fe 100644 --- a/app/views/admin/export_domain_blocks/new.html.haml +++ b/app/views/admin/export_domain_blocks/new.html.haml @@ -4,7 +4,10 @@ = simple_form_for @import, url: import_admin_export_domain_blocks_path, html: { multipart: true } do |f| .fields-row .fields-group.fields-row__column.fields-row__column-6 - = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data'), as: :file + = f.input :data, + as: :file, + hint: t('simple_form.hints.imports.data'), + wrapper: :with_block_label .actions = f.button :button, t('imports.upload'), type: :submit diff --git a/app/views/admin/follow_recommendations/show.html.haml b/app/views/admin/follow_recommendations/show.html.haml index 9c2063d3c5..9d23f9ba51 100644 --- a/app/views/admin/follow_recommendations/show.html.haml +++ b/app/views/admin/follow_recommendations/show.html.haml @@ -13,7 +13,8 @@ .filter-subset.filter-subset--with-select %strong= t('admin.follow_recommendations.language') .input.select.optional - = select_tag :language, options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, @language) + = select_tag :language, + options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, @language) .filter-subset %strong= t('admin.follow_recommendations.status') %ul @@ -30,9 +31,16 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if params[:status].blank? && can?(:suppress, :follow_recommendation) - = f.button safe_join([fa_icon('times'), t('admin.follow_recommendations.suppress')]), name: :suppress, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('times'), t('admin.follow_recommendations.suppress')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :suppress, + type: :submit - if params[:status] == 'suppressed' && can?(:unsuppress, :follow_recommendation) - = f.button safe_join([fa_icon('plus'), t('admin.follow_recommendations.unsuppress')]), name: :unsuppress, class: 'table-action-link', type: :submit + = f.button safe_join([fa_icon('plus'), t('admin.follow_recommendations.unsuppress')]), + class: 'table-action-link', + name: :unsuppress, + type: :submit .batch-table__body - if @accounts.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/ip_blocks/index.html.haml b/app/views/admin/ip_blocks/index.html.haml index a48e4791a3..f1d2b3dc47 100644 --- a/app/views/admin/ip_blocks/index.html.haml +++ b/app/views/admin/ip_blocks/index.html.haml @@ -14,7 +14,11 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if can?(:destroy, :ip_block) - = f.button safe_join([fa_icon('times'), t('admin.ip_blocks.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('times'), t('admin.ip_blocks.delete')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :delete, + type: :submit .batch-table__body - if @ip_blocks.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/ip_blocks/new.html.haml b/app/views/admin/ip_blocks/new.html.haml index ecaf04315b..81493012c6 100644 --- a/app/views/admin/ip_blocks/new.html.haml +++ b/app/views/admin/ip_blocks/new.html.haml @@ -5,16 +5,30 @@ = render 'shared/error_messages', object: @ip_block .fields-group - = f.input :ip, as: :string, wrapper: :with_block_label, input_html: { placeholder: '192.0.2.0/24' } + = f.input :ip, + as: :string, + input_html: { placeholder: '192.0.2.0/24' }, + wrapper: :with_block_label .fields-group - = f.input :expires_in, wrapper: :with_block_label, collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), label_method: ->(i) { I18n.t("admin.ip_blocks.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, + collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), + label_method: ->(i) { I18n.t("admin.ip_blocks.expires_in.#{i}") }, + prompt: I18n.t('invites.expires_in_prompt'), + wrapper: :with_block_label .fields-group - = f.input :severity, as: :radio_buttons, collection: IpBlock.severities.keys, include_blank: false, wrapper: :with_block_label, label_method: ->(severity) { ip_blocks_severity_label(severity) } + = f.input :severity, + as: :radio_buttons, + collection: IpBlock.severities.keys, + include_blank: false, + label_method: ->(severity) { ip_blocks_severity_label(severity) }, + wrapper: :with_block_label .fields-group - = f.input :comment, as: :string, wrapper: :with_block_label + = f.input :comment, + as: :string, + wrapper: :with_block_label .actions = f.button :button, t('admin.ip_blocks.add_new'), type: :submit diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml index f82cf26a38..8260430d80 100644 --- a/app/views/admin/relationships/index.html.haml +++ b/app/views/admin/relationships/index.html.haml @@ -30,7 +30,11 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), name: :suspend, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :suspend, + type: :submit .batch-table__body - if @accounts.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index b2982a42bf..3775a1101c 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -22,7 +22,9 @@ %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) - if status.edited? · - = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')), admin_account_status_path(status.account_id, status), class: 'detailed-status__datetime' + = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')), + admin_account_status_path(status.account_id, status), + class: 'detailed-status__datetime' - if status.discarded? · %span.negative-hint= t('admin.statuses.deleted') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 4376e5af4d..e20dd38c4f 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -37,7 +37,9 @@ %p = t 'admin.reports.statuses_description_html' — - = link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), admin_account_statuses_path(@report.target_account_id, report_id: @report.id), class: 'table-action-link' + = link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), + admin_account_statuses_path(@report.target_account_id, report_id: @report.id), + class: 'table-action-link' = form_for(@form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id)) do |f| .batch-table diff --git a/app/views/admin/roles/_form.html.haml b/app/views/admin/roles/_form.html.haml index 46a1c537a7..0b1c310162 100644 --- a/app/views/admin/roles/_form.html.haml +++ b/app/views/admin/roles/_form.html.haml @@ -5,19 +5,25 @@ = t('admin.roles.everyone_full_description_html') - else .fields-group - = form.input :name, wrapper: :with_label + = form.input :name, + wrapper: :with_label - unless current_user.role == form.object .fields-group - = form.input :position, wrapper: :with_label, input_html: { max: current_user.role.position - 1 } + = form.input :position, + input_html: { max: current_user.role.position - 1 }, + wrapper: :with_label .fields-group - = form.input :color, wrapper: :with_label, input_html: { placeholder: '#000000', type: 'color' } + = form.input :color, + input_html: { placeholder: '#000000', type: 'color' }, + wrapper: :with_label %hr.spacer/ .fields-group - = form.input :highlighted, wrapper: :with_label + = form.input :highlighted, + wrapper: :with_label %hr.spacer/ @@ -31,6 +37,17 @@ - (form.object.everyone? ? UserRole::Flags::CATEGORIES.slice(:invites) : UserRole::Flags::CATEGORIES).each do |category, permissions| %h4= t(category, scope: 'admin.roles.categories') - = form.input :permissions_as_keys, collection: permissions, wrapper: :with_block_label, include_blank: false, label_method: ->(privilege) { privilege_label(privilege) }, required: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label: false, hint: false, disabled: disable_permissions?(permissions) + = form.input :permissions_as_keys, + as: :check_boxes, + collection_wrapper_tag: 'ul', + collection: permissions, + disabled: disable_permissions?(permissions), + hint: false, + include_blank: false, + item_wrapper_tag: 'li', + label_method: ->(privilege) { privilege_label(privilege) }, + label: false, + required: false, + wrapper: :with_block_label %hr.spacer/ diff --git a/app/views/admin/settings/about/show.html.haml b/app/views/admin/settings/about/show.html.haml index 1237c20fa8..1eb47a0b54 100644 --- a/app/views/admin/settings/about/show.html.haml +++ b/app/views/admin/settings/about/show.html.haml @@ -11,7 +11,10 @@ %p.lead= t('admin.settings.about.preamble') .fields-group - = f.input :site_extended_description, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + = f.input :site_extended_description, + as: :text, + input_html: { rows: 8 }, + wrapper: :with_block_label %p.hint = t 'admin.settings.about.rules_hint' @@ -19,15 +22,32 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :show_domain_blocks, + collection_wrapper_tag: 'ul', + collection: %i(disabled users all), + include_blank: false, + item_wrapper_tag: 'li', + label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' + = f.input :show_domain_blocks_rationale, + collection_wrapper_tag: 'ul', + collection: %i(disabled users all), + include_blank: false, + item_wrapper_tag: 'li', + label_method: ->(value) { t("admin.settings.domain_blocks.#{value}") }, + wrapper: :with_label .fields-group - = f.input :status_page_url, wrapper: :with_block_label, input_html: { placeholder: "https://status.#{Rails.configuration.x.local_domain}" } + = f.input :status_page_url, + input_html: { placeholder: "https://status.#{Rails.configuration.x.local_domain}" }, + wrapper: :with_block_label .fields-group - = f.input :site_terms, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + = f.input :site_terms, + as: :text, + input_html: { rows: 8 }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/appearance/show.html.haml b/app/views/admin/settings/appearance/show.html.haml index 37a5d7e0c7..d5e6c13bc9 100644 --- a/app/views/admin/settings/appearance/show.html.haml +++ b/app/views/admin/settings/appearance/show.html.haml @@ -23,11 +23,16 @@ group_method: :last .fields-group - = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 } + = f.input :custom_css, + as: :text, + input_html: { rows: 8 }, + wrapper: :with_block_label .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :mascot, as: :file, wrapper: :with_block_label + = f.input :mascot, + as: :file, + wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group - if @admin_settings.mascot.persisted? diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index aee7306892..769c0dafe8 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -11,20 +11,28 @@ %p.lead= t('admin.settings.branding.preamble') .fields-group - = f.input :site_title, wrapper: :with_label + = f.input :site_title, + wrapper: :with_label .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :site_contact_username, wrapper: :with_label + = f.input :site_contact_username, + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :site_contact_email, wrapper: :with_label + = f.input :site_contact_email, + wrapper: :with_label .fields-group - = f.input :site_short_description, wrapper: :with_block_label, as: :text, input_html: { rows: 2, maxlength: 200 } + = f.input :site_short_description, + as: :text, + input_html: { rows: 2, maxlength: 200 }, + wrapper: :with_block_label .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :thumbnail, as: :file, wrapper: :with_block_label + = f.input :thumbnail, + as: :file, + wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group - if @admin_settings.thumbnail.persisted? = image_tag @admin_settings.thumbnail.file.url(:'@1x'), class: 'fields-group__thumbnail' diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 5a67016148..4b2ee572e3 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -11,9 +11,17 @@ %p.lead= t('admin.settings.content_retention.preamble') .fields-group - = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }, hint: false, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period') - = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :media_cache_retention_period, + input_html: { pattern: '[0-9]+' }, + wrapper: :with_block_label + = f.input :content_cache_retention_period, + hint: false, + input_html: { pattern: '[0-9]+' }, + warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period'), + wrapper: :with_block_label + = f.input :backups_retention_period, + input_html: { pattern: '[0-9]+' }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/discovery/show.html.haml b/app/views/admin/settings/discovery/show.html.haml index 12c122393b..f95c6d22a3 100644 --- a/app/views/admin/settings/discovery/show.html.haml +++ b/app/views/admin/settings/discovery/show.html.haml @@ -13,13 +13,20 @@ %h4= t('admin.settings.discovery.trends') .fields-group - = f.input :trends, as: :boolean, wrapper: :with_label + = f.input :trends, + as: :boolean, + wrapper: :with_label .fields-group - = f.input :trends_as_landing_page, as: :boolean, wrapper: :with_label + = f.input :trends_as_landing_page, + as: :boolean, + wrapper: :with_label .fields-group - = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, recommended: :not_recommended + = f.input :trendable_by_default, + as: :boolean, + wrapper: :with_label, + recommended: :not_recommended .fields-group = f.input :trending_status_cw, as: :boolean, wrapper: :with_label, label: t('admin.settings.trending_status_cw.title'), hint: t('admin.settings.trending_status_cw.desc_html'), glitch_only: true @@ -27,35 +34,57 @@ %h4= t('admin.settings.discovery.public_timelines') .fields-group - = f.input :timeline_preview, as: :boolean, wrapper: :with_label + = f.input :timeline_preview, + as: :boolean, + wrapper: :with_label .fields-group - = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html') + = f.input :noindex, + as: :boolean, + hint: t('admin.settings.default_noindex.desc_html'), + label: t('admin.settings.default_noindex.title'), + wrapper: :with_label %h4= t('admin.settings.discovery.publish_statistics') .fields-group - = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, recommended: :recommended + = f.input :activity_api_enabled, + as: :boolean, + wrapper: :with_label, + recommended: :recommended %h4= t('admin.settings.discovery.publish_discovered_servers') .fields-group - = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, recommended: :recommended + = f.input :peers_api_enabled, + as: :boolean, + wrapper: :with_label, + recommended: :recommended %h4= t('admin.settings.security.federation_authentication') .fields-group - = f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: discovery_warning_hint_text, hint: discovery_hint_text, disabled: authorized_fetch_overridden?, recommended: discovery_recommended_value + = f.input :authorized_fetch, + as: :boolean, + disabled: authorized_fetch_overridden?, + hint: discovery_hint_text, + label: t('admin.settings.security.authorized_fetch'), + recommended: discovery_recommended_value, + warning_hint: discovery_warning_hint_text, + wrapper: :with_label %h4= t('admin.settings.discovery.follow_recommendations') .fields-group - = f.input :bootstrap_timeline_accounts, wrapper: :with_block_label + = f.input :bootstrap_timeline_accounts, + wrapper: :with_block_label %h4= t('admin.settings.discovery.profile_directory') .fields-group - = f.input :profile_directory, as: :boolean, wrapper: :with_label + = f.input :profile_directory, + as: :boolean, + wrapper: :with_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index 4ece27bf4e..4dbc5fbecf 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -14,17 +14,32 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :registrations_mode, collection: %w(open approved none), wrapper: :with_label, include_blank: false, label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }, warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint') + = f.input :registrations_mode, + collection: %w(open approved none), + include_blank: false, + label_method: ->(mode) { I18n.t("admin.settings.registrations_mode.modes.#{mode}") }, + warning_hint: I18n.t('admin.settings.registrations_mode.warning_hint'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations? + = f.input :require_invite_text, + as: :boolean, + disabled: !approved_registrations?, + wrapper: :with_label - if captcha_available? .fields-group - = f.input :captcha_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.captcha_enabled.title'), hint: t('admin.settings.captcha_enabled.desc_html') + = f.input :captcha_enabled, + as: :boolean, + hint: t('admin.settings.captcha_enabled.desc_html'), + label: t('admin.settings.captcha_enabled.title'), + wrapper: :with_label .fields-group - = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 } + = f.input :closed_registrations_message, + as: :text, + input_html: { rows: 2 }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index a2e3cbc0da..33a41bd365 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -33,7 +33,11 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - unless @statuses.empty? - = f.button safe_join([fa_icon('flag'), t('admin.statuses.batch.report')]), name: :report, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('flag'), t('admin.statuses.batch.report')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :report, + type: :submit .batch-table__body - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/trends/links/index.html.haml b/app/views/admin/trends/links/index.html.haml index e6ed9d95f6..965d2b2e56 100644 --- a/app/views/admin/trends/links/index.html.haml +++ b/app/views/admin/trends/links/index.html.haml @@ -13,7 +13,9 @@ .filter-subset.filter-subset--with-select %strong= t('admin.follow_recommendations.language') .input.select.optional - = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true + = select_tag :locale, + options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), + include_blank: true .filter-subset %strong= t('admin.trends.trending') %ul @@ -35,10 +37,26 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow_provider')]), name: :approve_providers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow_provider')]), name: :reject_providers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve, + type: :submit + = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow_provider')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve_providers, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow_provider')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject_providers, + type: :submit .batch-table__body - if @preview_cards.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml index d9ad12fc96..c91822fb74 100644 --- a/app/views/admin/trends/links/preview_card_providers/index.html.haml +++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml @@ -31,8 +31,16 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject, + type: :submit .batch-table__body - if @preview_card_providers.empty? diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml index 98f2e77090..095f3f2187 100644 --- a/app/views/admin/trends/statuses/_status.html.haml +++ b/app/views/admin/trends/statuses/_status.html.haml @@ -14,7 +14,9 @@ = fa_icon 'link' = media_attachment.file_file_name - = t('admin.trends.statuses.shared_by', count: status.reblogs_count + status.favourites_count, friendly_count: friendly_number_to_human(status.reblogs_count + status.favourites_count)) + = t 'admin.trends.statuses.shared_by', + count: status.reblogs_count + status.favourites_count, + friendly_count: friendly_number_to_human(status.reblogs_count + status.favourites_count) - if status.account.domain.present? · diff --git a/app/views/admin/trends/statuses/index.html.haml b/app/views/admin/trends/statuses/index.html.haml index bf04772f22..0891d15fcf 100644 --- a/app/views/admin/trends/statuses/index.html.haml +++ b/app/views/admin/trends/statuses/index.html.haml @@ -31,10 +31,26 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow_account')]), name: :approve_accounts, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow_account')]), name: :reject_accounts, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve, + type: :submit + = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow_account')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve_accounts, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow_account')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject_accounts, + type: :submit .batch-table__body - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' diff --git a/app/views/admin/trends/tags/index.html.haml b/app/views/admin/trends/tags/index.html.haml index 4730d20c18..effde7b0ec 100644 --- a/app/views/admin/trends/tags/index.html.haml +++ b/app/views/admin/trends/tags/index.html.haml @@ -25,8 +25,16 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :approve, + type: :submit + = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :reject, + type: :submit .batch-table__body - if @tags.empty? diff --git a/app/views/admin/users/roles/show.html.haml b/app/views/admin/users/roles/show.html.haml index 8216180607..f26640f2a1 100644 --- a/app/views/admin/users/roles/show.html.haml +++ b/app/views/admin/users/roles/show.html.haml @@ -3,7 +3,13 @@ = simple_form_for @user, url: admin_user_role_path(@user) do |f| .fields-group - = f.association :role, wrapper: :with_block_label, collection: UserRole.assignable, label_method: :name, include_blank: I18n.t('admin.accounts.change_role.no_role') + = f.association :role, + collection: UserRole.assignable, + include_blank: I18n.t('admin.accounts.change_role.no_role'), + label_method: :name, + wrapper: :with_block_label .actions - = f.button :button, t('generic.save_changes'), type: :submit + = f.button :button, + t('generic.save_changes'), + type: :submit diff --git a/app/views/admin/webhooks/_form.html.haml b/app/views/admin/webhooks/_form.html.haml index 6c4574fd3b..2b948b9a6a 100644 --- a/app/views/admin/webhooks/_form.html.haml +++ b/app/views/admin/webhooks/_form.html.haml @@ -1,10 +1,21 @@ = render 'shared/error_messages', object: form.object .fields-group - = form.input :url, wrapper: :with_block_label, input_html: { placeholder: 'https://' } + = form.input :url, + wrapper: :with_block_label, + input_html: { placeholder: 'https://' } .fields-group - = form.input :events, collection: Webhook::EVENTS, wrapper: :with_block_label, include_blank: false, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', disabled: Webhook::EVENTS.filter { |event| !current_user.role.can?(Webhook.permission_for_event(event)) } + = form.input :events, + collection: Webhook::EVENTS, + wrapper: :with_block_label, + include_blank: false, + as: :check_boxes, + collection_wrapper_tag: 'ul', + item_wrapper_tag: 'li', + disabled: Webhook::EVENTS.filter { |event| !current_user.role.can?(Webhook.permission_for_event(event)) } .fields-group - = form.input :template, wrapper: :with_block_label, input_html: { placeholder: '{ "content": "Hello {{object.username}}" }' } + = form.input :template, + wrapper: :with_block_label, + input_html: { placeholder: '{ "content": "Hello {{object.username}}" }' } diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml index a3260816ed..5b297a6a9e 100644 --- a/app/views/filters/_filter_fields.html.haml +++ b/app/views/filters/_filter_fields.html.haml @@ -1,16 +1,37 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :title, as: :string, wrapper: :with_label, hint: false + = f.input :title, + as: :string, + hint: false, + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt') + = f.input :expires_in, + collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), + include_blank: I18n.t('invites.expires_in_prompt'), + label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, + wrapper: :with_label .fields-group - = f.input :context, wrapper: :with_block_label, collection: CustomFilter::VALID_CONTEXTS, as: :check_boxes, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', label_method: ->(context) { I18n.t("filters.contexts.#{context}") }, include_blank: false + = f.input :context, + as: :check_boxes, + collection_wrapper_tag: 'ul', + collection: CustomFilter::VALID_CONTEXTS, + include_blank: false, + item_wrapper_tag: 'li', + label_method: ->(context) { I18n.t("filters.contexts.#{context}") }, + wrapper: :with_block_label %hr.spacer/ .fields-group - = f.input :filter_action, as: :radio_buttons, collection: %i(warn hide), include_blank: false, wrapper: :with_block_label, label_method: ->(action) { filter_action_label(action) }, hint: t('simple_form.hints.filters.action'), required: true + = f.input :filter_action, + as: :radio_buttons, + collection: %i(warn hide), + hint: t('simple_form.hints.filters.action'), + include_blank: false, + label_method: ->(action) { filter_action_label(action) }, + required: true, + wrapper: :with_block_label %hr.spacer/ diff --git a/app/views/statuses_cleanup/show.html.haml b/app/views/statuses_cleanup/show.html.haml index bd4cc1d86c..07e833537e 100644 --- a/app/views/statuses_cleanup/show.html.haml +++ b/app/views/statuses_cleanup/show.html.haml @@ -7,9 +7,19 @@ = simple_form_for @policy, url: statuses_cleanup_path, method: :put, html: { id: 'edit_policy' } do |f| .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :enabled, as: :boolean, wrapper: :with_label, label: t('statuses_cleanup.enabled'), hint: t('statuses_cleanup.enabled_hint') + = f.input :enabled, + as: :boolean, + hint: t('statuses_cleanup.enabled_hint'), + label: t('statuses_cleanup.enabled'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :min_status_age, wrapper: :with_label, label: t('statuses_cleanup.min_age_label'), collection: AccountStatusesCleanupPolicy::ALLOWED_MIN_STATUS_AGE.map(&:to_i), label_method: ->(i) { t("statuses_cleanup.min_age.#{i}") }, include_blank: false, hint: false + = f.input :min_status_age, + collection: AccountStatusesCleanupPolicy::ALLOWED_MIN_STATUS_AGE.map(&:to_i), + hint: false, + include_blank: false, + label_method: ->(i) { t("statuses_cleanup.min_age.#{i}") }, + label: t('statuses_cleanup.min_age_label'), + wrapper: :with_label .flash-message= t('statuses_cleanup.explanation') @@ -17,28 +27,54 @@ .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_pinned, wrapper: :with_label, label: t('statuses_cleanup.keep_pinned'), hint: t('statuses_cleanup.keep_pinned_hint') + = f.input :keep_pinned, + hint: t('statuses_cleanup.keep_pinned_hint'), + label: t('statuses_cleanup.keep_pinned'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_direct, wrapper: :with_label, label: t('statuses_cleanup.keep_direct'), hint: t('statuses_cleanup.keep_direct_hint') + = f.input :keep_direct, + hint: t('statuses_cleanup.keep_direct_hint'), + label: t('statuses_cleanup.keep_direct'), + wrapper: :with_label .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_self_fav, wrapper: :with_label, label: t('statuses_cleanup.keep_self_fav'), hint: t('statuses_cleanup.keep_self_fav_hint') + = f.input :keep_self_fav, + hint: t('statuses_cleanup.keep_self_fav_hint'), + label: t('statuses_cleanup.keep_self_fav'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_self_bookmark, wrapper: :with_label, label: t('statuses_cleanup.keep_self_bookmark'), hint: t('statuses_cleanup.keep_self_bookmark_hint') + = f.input :keep_self_bookmark, + hint: t('statuses_cleanup.keep_self_bookmark_hint'), + label: t('statuses_cleanup.keep_self_bookmark'), + wrapper: :with_label .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_polls, wrapper: :with_label, label: t('statuses_cleanup.keep_polls'), hint: t('statuses_cleanup.keep_polls_hint') + = f.input :keep_polls, + hint: t('statuses_cleanup.keep_polls_hint'), + label: t('statuses_cleanup.keep_polls'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :keep_media, wrapper: :with_label, label: t('statuses_cleanup.keep_media'), hint: t('statuses_cleanup.keep_media_hint') + = f.input :keep_media, + hint: t('statuses_cleanup.keep_media_hint'), + label: t('statuses_cleanup.keep_media'), + wrapper: :with_label %h4= t('statuses_cleanup.interaction_exceptions') .fields-row .fields-row__column.fields-row__column-6.fields-group - = f.input :min_favs, wrapper: :with_label, label: t('statuses_cleanup.min_favs'), hint: t('statuses_cleanup.min_favs_hint'), input_html: { min: 1, placeholder: t('statuses_cleanup.ignore_favs') } + = f.input :min_favs, + hint: t('statuses_cleanup.min_favs_hint'), + input_html: { min: 1, placeholder: t('statuses_cleanup.ignore_favs') }, + label: t('statuses_cleanup.min_favs'), + wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group - = f.input :min_reblogs, wrapper: :with_label, label: t('statuses_cleanup.min_reblogs'), hint: t('statuses_cleanup.min_reblogs_hint'), input_html: { min: 1, placeholder: t('statuses_cleanup.ignore_reblogs') } + = f.input :min_reblogs, + hint: t('statuses_cleanup.min_reblogs_hint'), + input_html: { min: 1, placeholder: t('statuses_cleanup.ignore_reblogs') }, + label: t('statuses_cleanup.min_reblogs'), + wrapper: :with_label .flash-message= t('statuses_cleanup.interaction_exceptions_explanation') diff --git a/config/locales/devise.kab.yml b/config/locales/devise.kab.yml index f878a5b503..438c1df2b9 100644 --- a/config/locales/devise.kab.yml +++ b/config/locales/devise.kab.yml @@ -81,9 +81,9 @@ kab: update_needs_confirmation: Tleqmeḍ akken iwata amiḍan-ik·im, maca nesra ad nsenqed tansa-ik·im imayl tamaynut. Ttxil-k·m senqed imayl-k·m sakin ḍfer aseɣwen i usentem n n tansa imayl tamaynut. Ttxil senqed akaram n spam ma yella ur tufiḍ ara imayl-nni. updated: Amiḍan-ik·im yettwalqem akken iwata. sessions: - already_signed_out: Aqla-k teffγeḍ. + already_signed_out: Aqla-k teffɣeḍ. signed_in: Aqla-k teqqneḍ. - signed_out: Aqla-k teffγeḍ. + signed_out: Aqla-k teffɣeḍ. unlocks: send_instructions: Deg kra n tesdatin, ad teṭṭfeḍ imayl deg-s iwellihen i yilaqen i userreḥ n umiḍan-ik·im. Ma yella ur tufiḍ ara izen-agi, ttxil-k·m ẓer deg ukaram spam. send_paranoid_instructions: Ma yella umiḍan-ik·im yella, ad teṭṭfeḍ imayl deg tesdatin i d-iteddun, deg-s iwellihen i yilaqen i userreḥ n umiḍan-ik·im. Ma yella ur tufiḍ ara izen-agi, ttxil-k·m ẓer deg ukaram spam. diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml index e79a83c431..4a7f346fab 100644 --- a/config/locales/devise.pt-BR.yml +++ b/config/locales/devise.pt-BR.yml @@ -53,6 +53,7 @@ pt-BR: subtitle: A autenticação de dois fatores foi desativada. title: 2FA desativada two_factor_enabled: + explanation: Será necessário um código gerado pelo aplicativo de autenticação para fazer login. subject: 'Mastodon: Autenticação de dois fatores ativada' subtitle: A autenticação de dois fatores foi ativada para sua conta. title: 2FA ativada @@ -74,6 +75,7 @@ pt-BR: title: Uma das suas chaves de segurança foi excluída webauthn_disabled: explanation: A autenticação por chaves de segurança foi desativada para sua conta. + extra: O login agora é possível usando o código gerado por um aplicativo de autenticação de dois fatores. subject: 'Mastodon: Autenticação por chaves de segurança desativada' title: Chaves de segurança desativadas webauthn_enabled: diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml index 7564bc2dc6..aa0eae2844 100644 --- a/config/locales/doorkeeper.gl.yml +++ b/config/locales/doorkeeper.gl.yml @@ -184,7 +184,7 @@ gl: write:blocks: bloquear contas e dominios write:bookmarks: marcar publicacións write:conversations: acalar e eliminar conversas - write:favourites: marcar como favorita + write:favourites: favorecer publicacións write:filters: crear filtros write:follows: seguir usuarias write:lists: crear listaxes diff --git a/config/locales/doorkeeper.kab.yml b/config/locales/doorkeeper.kab.yml index d7f8904a35..1b1a7df957 100644 --- a/config/locales/doorkeeper.kab.yml +++ b/config/locales/doorkeeper.kab.yml @@ -12,7 +12,7 @@ kab: attributes: redirect_uri: fragment_present: ur yezmir ad yegber afrur. - invalid_uri: ilaq ad tili d tansa URL tameγtut. + invalid_uri: ilaq ad tili d tansa URL tameɣtut. relative_uri: ilaq ad yili d URI amagdaz. secured_uri: ilaq URI ad yili HTTPS/SSL. doorkeeper: @@ -40,7 +40,7 @@ kab: name: Isem new: Asnas amaynut show: Ẓer - title: Isnasen-ik + title: Isnasen-ik·im new: title: Asnas amaynut show: @@ -64,6 +64,8 @@ kab: confirmations: revoke: Tetḥeqqeḍ? index: + description_html: Ha-t-an yisnasen i izemren ad kecmen ɣer umiḍan-ik·im, s useqdec n API. Ma llan yisnasen ur teεqileḍ ara da, neɣ kra n wesnas ur iteddu ara akken ilaq, tzemreḍ ad tekkseḍ anekcum-is. + last_used_at: Yettwaseqdec i tikkelt taneggarut ass n %{date} title: Isnasen-ik·im yettusirgen errors: messages: @@ -98,7 +100,7 @@ kab: application: title: Tlaq tsiregt n OAuth scopes: - admin:read: γeṛ akk isefka γef uqeddac + admin:read: ad iɣeṛ akk isefka ɣef uqeddac admin:write: ẓreg akk isefka γef uqeddac follow: beddel assaγen n umiḍan push: ṭṭef-d tilγa-ik yettwademren @@ -106,19 +108,19 @@ kab: read:accounts: ẓer isallen n yimiḍanen read:blocks: ẓer imiḍanen i tesḥebseḍ read:bookmarks: ẓer ticraḍ-ik - read:filters: ẓer imsizedgen-ik + read:filters: ad iẓer imsizdigen-ik·im read:follows: ẓer imeḍfaṛen-ik read:lists: ẓer tibdarin-ik·im read:mutes: ẓer wid i tesgugmeḍ - read:notifications: ẓer tilγa-ik + read:notifications: ad ẓer tilɣa-inek·inem read:reports: ẓer ineqqisen-ik·im read:search: anadi deg umkan-ik·im read:statuses: ẓer meṛṛa tisuffaɣ write: beddel meṛṛa isefka n umiḍan-ik - write:accounts: ẓreg amaγnu-ik + write:accounts: ad iẓreg amaɣnu-ik·im write:blocks: seḥbes imiḍanen d tγula write:bookmarks: ad yernu tisuffaɣ ɣer ticraḍ - write:filters: rnu-d imsizedgen + write:filters: ad isnulfu imsizedgen write:follows: ḍfeṛ imdanen write:lists: ad yesnulfu tibdarin write:media: ad yessali ifuyla n umidya diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 0948e8d434..3d7c8b9ea4 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -970,7 +970,7 @@ es-MX: admin_mailer: auto_close_registrations: body: Debido al faltante de actividad reciente de moderación, los registros en %{instance} han cambiado automáticamente para requerir la revisión manial, para evitar que %{instance} se utilice como una plataforma para potenciales malos actores. Puedes revertir este cambio en los registros en cualquier momento. - subject: Se ha cambiado automáticamente el registro de %{instance} para requerir aprobación + subject: Los registros para %{instance} han sido cambiados automáticamente para requerir aprobación new_appeal: actions: delete_statuses: para eliminar sus mensajes @@ -1855,15 +1855,15 @@ es-MX: feature_action: Leer más feature_audience: Mastodon te proporciona una posibilidad única de gestionar tu audiencia sin intermediarios. El despliegue de Mastodon en tu propia infraestructura te permite seguir y ser seguido desde cualquier servidor de Mastodon que se encuentre en línea y no está bajo el control de nadie más que tú. feature_audience_title: Construye tu audiencia con confianza - feature_control: Tú sabes lo que quieres ver en tu página principal. Nada de algoritmos y publicidad para desperdiciar tu tiempo. Sigue a quien quieras a través de cualquier servidor de Mastodon y recibe sus publicaciones en orden cronológico. Haz tu rincón de internet un poco más como tú. - feature_control_title: Mantente en control de tu línea de tiempo - feature_creativity: Mastodon soporta mensajes de audio, vídeo e imágenes, descripciones de accesibilidad, encuestas, advertencias de contenido, avatares animados, emojis personalizados, recortes de miniatura, y más, para ayudarte a expresarte en línea. Ya sea publicando tu arte, tu música o tu podcast, Mastodon está ahí para ti. + feature_control: Tú sabes mejor lo que quieres ver en tu página principal. Nada de algoritmos o publicidad para desperdiciar tu tiempo. Sigue a quien quieras a través de cualquier servidor de Mastodon y recibe sus publicaciones en orden cronológico. Haz tu rincón de internet un poco más como tú. + feature_control_title: Mantén el control de tu línea de tiempo + feature_creativity: Mastodon soporta publicaciones con audio, vídeo e imágenes, descripciones de accesibilidad, encuestas, advertencias de contenido, avatares animados, emojis personalizados, recortes de miniatura, y más, para ayudarte a expresarte en línea. Ya sea publicando tu arte, tu música o tu podcast, Mastodon está ahí para ti. feature_creativity_title: Creatividad inigualable - feature_moderation: Mastodon vuelve a poner la toma de decisiones en tus manos. Cada servidor crea sus propias reglas y reglamentos, que se aplican localmente y no globalmente como en redes sociales corporativas, lo que resulta en la mayor flexibilidad para responder a las necesidades de diferentes grupos de personas. Únete a un servidor con las reglas con las que esté sde acuerdo, o aloja el tuyo propio. + feature_moderation: Mastodon vuelve a poner la toma de decisiones en tus manos. Cada servidor crea sus propias reglas y reglamentos, las cuales se aplican localmente y no globalmente como en redes sociales corporativas, haciéndolos más flexibles para responder a las necesidades de diferentes grupos de personas. Únete a un servidor con las reglas que estés de acuerdo, o aloja el tuyo propio. feature_moderation_title: La moderación como debería ser follow_action: Seguir - follow_step: Seguir a personas interesantes es de lo que trata Mastodon. - follow_title: Personaliza tu línea de inicio + follow_step: Seguir a personas interesantes es de lo que se trata Mastodon. + follow_title: Personaliza tu inicio follows_subtitle: Seguir cuentas conocidas follows_title: A quién seguir follows_view_more: Ver más personas para seguir @@ -1871,10 +1871,10 @@ es-MX: hashtags_subtitle: Explora las tendencias de los últimos 2 días hashtags_title: Etiquetas en tendencia hashtags_view_more: Ver más etiquetas en tendencia - post_action: Redactar + post_action: Redacta post_step: Di hola al mundo con texto, fotos, vídeos o encuestas. - post_title: Escribe tu primera publicación - share_action: Compartir + post_title: Haz tu primera publicación + share_action: Comparte share_step: Dile a tus amigos cómo encontrarte en Mastodon. share_title: Comparte tu perfil de Mastodon sign_in_action: Regístrate diff --git a/config/locales/eu.yml b/config/locales/eu.yml index bd46e4aa88..4c88ac0e59 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -970,6 +970,9 @@ eu: title: Webhook-ak webhook: Webhook admin_mailer: + auto_close_registrations: + body: Duela gutxi moderatzaile gutxi aritu direla eta, %{instance} instantziako izen-emateek eskuzko berrikuspena beharko dute automatikoki, %{instance} instantzia eragile okerren plataforma gisa erabili dadin ekiditeko. Izen-emate irekiak berriro gai ditzakezu nahi duzunean. + subject: "%{instance} instantziako izen-emateek onarpena beharko dute orain" new_appeal: actions: delete_statuses: bidalketak ezabatzea diff --git a/config/locales/gl.yml b/config/locales/gl.yml index a6543005b2..61a6086c5e 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -907,7 +907,7 @@ gl: statuses: allow: Permitir publicación allow_account: Permitir autora - description_html: Estas son publicacións que o teu servidor coñece que están sendo compartidas e favorecidas en gran número neste intre. Pode ser útil para as persoas recén chegadas e as que retornan para que atopen persoas a quen seguir. Non se mostran publicamente a menos que aprobes a autora, e a autora permita que a súa conta sexa suxerida a outras. Tamén podes rexeitar ou aprobar publicacións individuais. + description_html: Estas son publicacións que o teu servidor coñece que están sendo compartidas e favorecidas en gran número neste intre. Pode ser útil para as persoas recén chegadas e para as que retornan para que atopen persoas a quen seguir. Non se mostran publicamente a menos que aprobes a autora, e a autora permita que a súa conta sexa suxerida a outras. Tamén podes rexeitar ou aprobar publicacións individuais. disallow: Rexeitar publicación disallow_account: Rexeitar autora no_status_selected: Non se cambiou ningunha publicación en voga xa que non había ningunha seleccionada @@ -1405,7 +1405,7 @@ gl: confirmation_html: Tes a certeza de querer retirar a subscrición a Mastodon en %{domain} para recibir %{type} no teu correo electrónico en %{email}? Poderás volver a subscribirte desde os axustes de notificacións por correo. emails: notification_emails: - favourite: notificacións de favoritos + favourite: notificacións de favorecidas follow: notificacións de seguimentos follow_request: notificacións de solicitudes de seguimento mention: notificacións de mencións @@ -1464,7 +1464,7 @@ gl: sign_up: subject: "%{name} rexistrouse" favourite: - body: 'A túa publicación foi marcada como favorita por %{name}:' + body: 'A túa publicación foi favorecida por %{name}:' subject: "%{name} marcou como favorita a túa publicación" title: Nova favorita follow: @@ -1717,7 +1717,7 @@ gl: ignore_favs: Ignorar favoritas ignore_reblogs: Ignorar promocións interaction_exceptions: Excepcións baseadas en interaccións - interaction_exceptions_explanation: Ten en conta de que non hai garantía de que se eliminen as túas publicacións se non superan o límite de promocións e favorecementos aínda que algunha vez o tivesen superado. + interaction_exceptions_explanation: Ten en conta que non hai garantía de que se eliminen as túas publicacións se baixan do límite de promocións e favorecementos se nalgún momento o superaron. keep_direct: Manter mensaxes directas keep_direct_hint: Non borrar ningunha das túas mensaxes directas keep_media: Manter publicacións que conteñen multimedia @@ -1728,7 +1728,7 @@ gl: keep_polls_hint: Non eliminar ningunha das túas enquisas keep_self_bookmark: Manter as publicacións engadidas a marcadores keep_self_bookmark_hint: Non elimina as publicacións se as engadiches aos marcadores - keep_self_fav: Manter as publicacións que marcaches como favoritas + keep_self_fav: Manter as publicacións que favoreceches keep_self_fav_hint: Non elimina as túas propias publicacións se as marcaches como favoritas min_age: '1209600': 2 semanas @@ -1740,7 +1740,7 @@ gl: '63113904': 2 anos '7889238': 3 meses min_age_label: Límite temporal - min_favs: Manter as publicacións favoritas máis de + min_favs: Manter as publicacións favorecidas polo menos min_favs_hint: Non elimina ningunha das túas publicacións que recibiron alomenos esta cantidade de favorecementos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos min_reblogs: Manter publicacións promovidas máis de min_reblogs_hint: Non elimina ningunha das túas publicacións se foron promovidas máis deste número de veces. Deixa en branco para eliminar publicacións independentemente do seu número de promocións diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 66c544f9e7..b19424c0fa 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -252,6 +252,7 @@ kab: add_new: Timerna n taɣult ɣer tabdert tamellalt created_msg: Taγult-a tettwarna γer wumuγ amellal mebla ugur destroyed_msg: Taγult-a tettwakkes seg umuγ amellal + export: Sifeḍ import: Kter undo: Kkes seg tebdart tamellalt domain_blocks: @@ -387,6 +388,7 @@ kab: roles: categories: administration: Tadbelt + invites: Iɛeṛṛuḍen moderation: Aseɣyed delete: Kkes privileges: @@ -420,7 +422,10 @@ kab: delete: Kkes afaylu yulin software_updates: documentation_link: Issin ugar + type: Anaw + version: Lqem statuses: + account: Ameskar application: Asnas back_to_account: Tuɣalin ɣer usebter n umiḍan deleted: Yettwakkes @@ -462,6 +467,7 @@ kab: advanced_web_interface: Agrudem n web leqqayen discovery: Asnirem localization: + body: Mastodon suqqlen-t-id yiwiziwen. guide_link: https://crowdin.com/project/mastodon guide_link_text: Yal yiwen·t y·tezmer a ttekki. sensitive_content: Agbur amḥulfu @@ -471,12 +477,18 @@ kab: view_profile: Ssken-d amaɣnu view_status: Ssken-d tasuffiɣt applications: + created: Yennulfa-d wesnas akken iwata + destroyed: Yettwakkes wesnas-nni akken iwata logout: Ffeɣ token_regenerated: Ajuṭu n unekcum yettusirew i tikkelt-nniḍen akken iwata your_token: Ajiṭun-ik·im n unekcum auth: apply_for_account: Suter amiḍan + captcha_confirmation: + title: Asefqed n tɣellist confirmations: + clicking_this_link: tekki ɣef wassaɣ-a + proceed_to_login_html: Tzemreḍ tura ad tkemmleḍ ɣer %{login_link}. welcome_title: Ansuf yessek·em, %{name}! delete_account: Kkes amiḍan description: @@ -507,6 +519,7 @@ kab: rules: accept: Qbel back: Tuɣalin + invited_by: 'Tzemreḍ ad tkecmeḍ ɣer %{domain} s tanemmirt i tinnubga i d-teṭṭfeḍ sɣur :' preamble_invited: Uqbel ad tkemmleḍ, ttxil-k·m ẓer ilugan i d-sbedden yimkariyen n %{domain}. title: Kra n yilugan igejdanen. title_invited: Tettwaɛerḍeḍ. @@ -521,7 +534,7 @@ kab: preamble_html: Kcem ar %{domain} s inekcam-inek n tuqqna. Ma yella yezga-d umiḍan-ik deg uqeddac-nniḍen, ur tezmireḍ ara ad tkecmeḍ sya. title: Akeččum ɣer %{domain} sign_up: - preamble: S umiḍan deg uqeddac-a n Mastodon, ad tizmireḍ ad tḍefreḍ win i ak-yehwan deg uẓeṭṭa, anida yebɣu yili umiḍan-nsen. + preamble: S umiḍan deg uqeddac-a n Mastodon, ad tizmireḍ ad tḍefreḍ win i ak·kem-yehwan deg uẓeṭṭa, anida yebɣu yili umiḍan-nnsen. title: Iyya ad d-nessewjed tiɣawsiwin i %{domain}. status: account_status: Addad n umiḍan @@ -531,6 +544,9 @@ kab: confirm: Kemmel invalid_password: Yir awal uffir prompt: Sentem awal uffir send ad tkemleḍ + crypto: + errors: + invalid_key: maci d tasarut tameɣtut n Ed25519 neɣ Curve25519 date: formats: default: "%d %b %Y" @@ -550,6 +566,7 @@ kab: x_months: "%{count}agu" x_seconds: "%{count}tas" deletes: + challenge_not_passed: Ur iṣeḥḥa ara yisalli-nni i teskecmeḍ confirm_password: Sekcem awal-ik·im uffir n tura akken ad tesfeqdeḍ tamagit-ik·im proceed: Kkes amiḍan warning: @@ -607,10 +624,15 @@ kab: confirm: Sentem copy: Nɣel delete: Kkes + none: Ula yiwen order_by: Sizwer s save_changes: Sekles ibeddilen today: ass-a imports: + errors: + empty: Afaylu CSV d ilem + too_large: Bezzaf meqqer ufaylu + failures: Tuccḍiwin modes: merge: Smezdi overwrite: Semselsi @@ -707,6 +729,8 @@ kab: relationship: Assaɣ remove_selected_follows: Ur ṭṭafar ara iseqdacen yettwafernen status: Addad n umiḍan + rss: + content_warning: 'Alɣu n ugbur :' sessions: activity: Armud aneggaru browser: Iminig @@ -843,6 +867,8 @@ kab: silence: Amiḍan yesɛa talast suspend: Amiḍan yettwaḥbas welcome: + apps_android_action: Awi-t-id seg Google Play + apps_ios_action: Sader-it-id seg App Store apps_step: Zdem-d isnasen-nneɣ unṣiben. apps_title: Isnasen n Mastodon feature_action: Issin ugar diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index a521af185e..82b970ce58 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -775,6 +775,7 @@ pt-BR: approved: Aprovação necessária para criar conta none: Ninguém pode criar conta open: Qualquer um pode criar conta + warning_hint: Recomendamos o uso de "Aprovação necessária para se cadastrar", a menos que você esteja confiante de que sua equipe de moderação pode lidar com spam e registros maliciosos em tempo hábil. security: authorized_fetch: Exigir autenticação por parte de servidores federados authorized_fetch_hint: Exigir autenticação de servidores federados permite uma aplicação mais rigorosa de bloqueios tanto de nível de usuário como de servidor. No entanto, isso traz como custo uma penalidade no desempenho, reduz o alcance das suas respostas e pode introduzir problemas de compatibilidade com alguns serviços federados. Além disso, não impedirá atores dedicados de consultar suas publicações e contas públicas. @@ -967,6 +968,9 @@ pt-BR: title: Webhooks webhook: Webhook admin_mailer: + auto_close_registrations: + body: Devido à falta de atividade recente dos moderadores, as inscrições em %{instance} foram automaticamente alteradas para exigir revisão manual, para evitar que %{instance} seja usada como uma plataforma para potenciais atores mal-intencionados. Você pode alterá-la de volta para inscrições abertas a qualquer momento. + subject: As inscrições para %{instance} foram automaticamente alteradas para requerer aprovação new_appeal: actions: delete_statuses: para excluir suas publicações @@ -1781,6 +1785,7 @@ pt-BR: action: Configurações da conta explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi aprovada. Sua conta está novamente em situação regular. subject: Sua revisão de %{date} foi aprovada + subtitle: Sua conta está novamente em boa situação. title: Revisão aprovada appeal_rejected: explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi rejeitada. @@ -1837,7 +1842,41 @@ pt-BR: silence: Conta silenciada suspend: Conta banida welcome: + apps_android_action: Disponível no Google Play + apps_ios_action: Disponível na App Store + apps_step: Baixe nossos aplicativos oficiais. + apps_title: Apps Mastodon + checklist_subtitle: 'Vamos começar nesta nova fronteira social:' + checklist_title: Lista de verificação de boas-vindas + edit_profile_action: Personalizar + edit_profile_step: Aumente suas interações tendo um perfil completo. + edit_profile_title: Customize seu perfil explanation: Aqui estão algumas dicas para você começar + feature_action: Saiba mais + feature_audience: O Mastodon oferece a você uma oportunidade única de gerenciar seu público sem intermediários. O Mastodon implantado em sua própria infraestrutura permite que você siga e seja seguido por qualquer outro servidor Mastodon online e está única e exclusivamente sob o seu controle. + feature_audience_title: Construa confiança com seu público + feature_control: Você sabe o que deseja ver na sua página inicial. Sem algoritmos ou anúncios para desperdiçar seu tempo. Siga qualquer pessoa em qualquer servidor Mastodon a partir de uma única conta e receba suas postagens em ordem cronológica, e faça o seu cantinho na internet um pouco mais a sua cara. + feature_control_title: Fique no controle da sua própria linha do tempo + feature_creativity: Mastodon oferece suporte a postagens de áudio, vídeo e imagem, descrições de acessibilidade, enquetes, avisos de conteúdo, avatares animados, emojis personalizados, recorte de miniaturas e muito mais, para ajudá-lo a se expressar online. Seja você publicando sua arte, sua música ou seu podcast, o Mastodon está lá para você. + feature_creativity_title: Criatividade inigualável + feature_moderation: Mastodon devolve a tomada de decisão para suas mãos. Cada servidor cria suas próprias regras e regulamentos, que são aplicados localmente e não de cima para baixo como nas redes sociais corporativas, tornando-o o mais flexível em responder às necessidades de diferentes grupos de pessoas. Junte-se a um servidor com as regras com as quais você concorda, ou hospede o seu próprio. + feature_moderation_title: Moderando como deve ser + follow_action: Seguir + follow_step: Seguir pessoas interessantes é do que trata Mastodon. + follow_title: Personalize sua página inicial + follows_subtitle: Siga contas conhecidas + follows_title: Quem seguir + follows_view_more: Veja mais pessoas para seguir + hashtags_subtitle: Explorar o que está em alta nos últimos 2 dias + hashtags_title: Hashtags em alta + hashtags_view_more: Ver mais hashtags em alta + post_action: Escrever + post_step: Diga olá para o mundo com texto, fotos, vídeos ou enquetes. + post_title: Crie sua primeira publicação + share_action: Compartilhar + share_step: Deixe seus amigos saberem como te encontrar no Mastodon. + share_title: Compartilhe seu perfil do Mastodon + sign_in_action: Entrar subject: Boas-vindas ao Mastodon title: Boas vindas, %{name}! users: diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index 8e63211b65..4f7357c572 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -53,6 +53,7 @@ kab: ends_at: Tagara n tedyant text: Alɣu defaults: + autofollow: Ɛreḍ-it-id ad yeḍfer amiḍan-ik·im avatar: Avaṭar bot: Wagi d amiḍan aṛubut chosen_languages: Sizdeg tutlayin diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 133a225405..596fbe3e34 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -26,7 +26,7 @@ lv: disable: Neļauj lietotājam izmantot savu kontu, bet neizdzēs vai neslēp tā saturu. none: Izmanto šo, lai nosūtītu lietotājam brīdinājumu, neradot nekādas citas darbības. sensitive: Piespiest visus šī lietotāja multivides pielikumus atzīmēt kā sensitīvus. - silence: Neļauj lietotājam publicēt ziņas ar publisku redzamību, paslēp viņa ziņas un paziņojumus no personām, kas viņiem neseko. Tiek aizvērti visi šī konta pārskati. + silence: Neļaut lietotājam veikt ierakstus ar publisku redzamību, paslēpt viņa ierakstus un paziņojumus no cilvēkiem, kas tam neseko. Tiek aizvērti visi ziņojumi par šo kontu. suspend: Novērs jebkādu mijiedarbību no šī konta vai uz to un dzēs tā saturu. Atgriežams 30 dienu laikā. Tiek aizvērti visi šī konta pārskati. warning_preset_id: Neobligāts. Tu joprojām vari pievienot pielāgotu tekstu sākotnējās iestatīšanas beigās announcement: diff --git a/lib/mastodon/cli/search.rb b/lib/mastodon/cli/search.rb index 5901c07776..3a73c9c047 100644 --- a/lib/mastodon/cli/search.rb +++ b/lib/mastodon/cli/search.rb @@ -100,6 +100,14 @@ module Mastodon::CLI progress.finish say("Indexed #{added} records, de-indexed #{removed}", :green, true) + rescue Elasticsearch::Transport::Transport::ServerError => e + fail_with_message <<~ERROR + There was an issue connecting to the search server. Make sure the + server is configured and running correctly, and that the environment + variable settings match what the server is expecting. + + #{e.message} + ERROR end private diff --git a/spec/lib/mastodon/cli/search_spec.rb b/spec/lib/mastodon/cli/search_spec.rb index 8cce2c6ee2..ed3789c3e7 100644 --- a/spec/lib/mastodon/cli/search_spec.rb +++ b/spec/lib/mastodon/cli/search_spec.rb @@ -33,6 +33,17 @@ describe Mastodon::CLI::Search do end end + context 'when server communication raises an error' do + let(:options) { { reset_chewy: true } } + + before { allow(Chewy::Stash::Specification).to receive(:reset!).and_raise(Elasticsearch::Transport::Transport::Errors::InternalServerError) } + + it 'Exits with error message' do + expect { subject } + .to raise_error(Thor::Error, /issue connecting to the search/) + end + end + context 'without options' do before { stub_search_indexes } diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb index 62543157c3..c6c2d56f36 100644 --- a/spec/requests/api/v1/blocks_spec.rb +++ b/spec/requests/api/v1/blocks_spec.rb @@ -38,16 +38,14 @@ RSpec.describe 'Blocks' do expect(body_as_json.size).to eq(params[:limit]) end - it 'sets the correct pagination header for the prev path' do + it 'sets correct link header pagination' do subject - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id)) - end - - it 'sets the correct pagination header for the next path' do - subject - - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_blocks_url(limit: params[:limit], max_id: blocks[1].id)) + expect(response) + .to include_pagination_headers( + prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id), + next: api_v1_blocks_url(limit: params[:limit], max_id: blocks.second.id) + ) end end diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb index 18f4fddc29..dc32820c89 100644 --- a/spec/requests/api/v1/bookmarks_spec.rb +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -42,9 +42,14 @@ RSpec.describe 'Bookmarks' do it 'paginates correctly', :aggregate_failures do subject - expect(body_as_json.size).to eq(params[:limit]) - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id)) - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks[1].id)) + expect(body_as_json.size) + .to eq(params[:limit]) + + expect(response) + .to include_pagination_headers( + prev: api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id), + next: api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks.second.id) + ) end end diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb index 2d8a42e716..b988ac99db 100644 --- a/spec/requests/api/v1/favourites_spec.rb +++ b/spec/requests/api/v1/favourites_spec.rb @@ -45,16 +45,14 @@ RSpec.describe 'Favourites' do expect(body_as_json.size).to eq(params[:limit]) end - it 'sets the correct pagination header for the prev path' do + it 'sets the correct pagination headers' do subject - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id)) - end - - it 'sets the correct pagination header for the next path' do - subject - - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_favourites_url(limit: params[:limit], max_id: favourites[1].id)) + expect(response) + .to include_pagination_headers( + prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id), + next: api_v1_favourites_url(limit: params[:limit], max_id: favourites.second.id) + ) end end diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb index 52ed1ba4bb..3d2d82d5db 100644 --- a/spec/requests/api/v1/followed_tags_spec.rb +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -49,16 +49,14 @@ RSpec.describe 'Followed tags' do expect(body_as_json.size).to eq(params[:limit]) end - it 'sets the correct pagination header for the prev path' do + it 'sets the correct pagination headers' do subject - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], since_id: tag_follows.last.id)) - end - - it 'sets the correct pagination header for the next path' do - subject - - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], max_id: tag_follows.last.id)) + expect(response) + .to include_pagination_headers( + prev: api_v1_followed_tags_url(limit: params[:limit], since_id: tag_follows.last.id), + next: api_v1_followed_tags_url(limit: params[:limit], max_id: tag_follows.last.id) + ) end end end diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb index b2782a0c22..019bf16584 100644 --- a/spec/requests/api/v1/mutes_spec.rb +++ b/spec/requests/api/v1/mutes_spec.rb @@ -44,10 +44,11 @@ RSpec.describe 'Mutes' do it 'sets the correct pagination headers', :aggregate_failures do subject - headers = response.headers['Link'] - - expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id.to_s)) - expect(headers.find_link(%w(rel next)).href).to eq(api_v1_mutes_url(limit: params[:limit], max_id: mutes.last.id.to_s)) + expect(response) + .to include_pagination_headers( + prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id), + next: api_v1_mutes_url(limit: params[:limit], max_id: mutes.last.id) + ) end end diff --git a/spec/requests/api/v1/notifications_spec.rb b/spec/requests/api/v1/notifications_spec.rb index 222ff67fc8..55d3cdac94 100644 --- a/spec/requests/api/v1/notifications_spec.rb +++ b/spec/requests/api/v1/notifications_spec.rb @@ -98,9 +98,14 @@ RSpec.describe 'Notifications' do notifications = user.account.notifications - expect(body_as_json.size).to eq(params[:limit]) - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id.to_s)) - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id.to_s)) + expect(body_as_json.size) + .to eq(params[:limit]) + + expect(response) + .to include_pagination_headers( + prev: api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id), + next: api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id) + ) end end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb index e57e9643bf..2bebe8cf45 100644 --- a/spec/requests/api/v1/timelines/home_spec.rb +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -55,10 +55,11 @@ describe 'Home', :sidekiq_inline do it 'sets the correct pagination headers', :aggregate_failures do subject - headers = response.headers['Link'] - - expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_home_url(limit: 1, min_id: ana.statuses.first.id.to_s)) - expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_home_url(limit: 1, max_id: ana.statuses.first.id.to_s)) + expect(response) + .to include_pagination_headers( + prev: api_v1_timelines_home_url(limit: params[:limit], min_id: ana.statuses.first.id), + next: api_v1_timelines_home_url(limit: params[:limit], max_id: ana.statuses.first.id) + ) end end end diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb index 4afa40e580..d6e1b69759 100644 --- a/spec/requests/api/v1/timelines/public_spec.rb +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -87,10 +87,11 @@ describe 'Public' do it 'sets the correct pagination headers', :aggregate_failures do subject - headers = response.headers['Link'] - - expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_public_url(limit: 1, min_id: media_status.id.to_s)) - expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_public_url(limit: 1, max_id: media_status.id.to_s)) + expect(response) + .to include_pagination_headers( + prev: api_v1_timelines_public_url(limit: params[:limit], min_id: media_status.id), + next: api_v1_timelines_public_url(limit: params[:limit], max_id: media_status.id) + ) end end end diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb index a8f20213eb..e55221197c 100644 --- a/spec/requests/api/v1/timelines/tag_spec.rb +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -75,10 +75,11 @@ RSpec.describe 'Tag' do it 'sets the correct pagination headers', :aggregate_failures do subject - headers = response.headers['Link'] - - expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_tag_url(limit: 1, min_id: love_status.id.to_s)) - expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_tag_url(limit: 1, max_id: love_status.id.to_s)) + expect(response) + .to include_pagination_headers( + prev: api_v1_timelines_tag_url(limit: params[:limit], min_id: love_status.id), + next: api_v1_timelines_tag_url(limit: params[:limit], max_id: love_status.id) + ) end end diff --git a/spec/services/bulk_import_row_service_spec.rb b/spec/services/bulk_import_row_service_spec.rb index a77acc0732..d295c28067 100644 --- a/spec/services/bulk_import_row_service_spec.rb +++ b/spec/services/bulk_import_row_service_spec.rb @@ -110,7 +110,7 @@ RSpec.describe BulkImportRowService do end it 'adds the target account to the list' do - expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + expect { subject.call(import_row) }.to add_target_account_to_list end end @@ -124,7 +124,7 @@ RSpec.describe BulkImportRowService do end it 'adds the target account to the list' do - expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + expect { subject.call(import_row) }.to add_target_account_to_list end end @@ -134,7 +134,7 @@ RSpec.describe BulkImportRowService do end it 'adds the target account to the list' do - expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + expect { subject.call(import_row) }.to add_target_account_to_list end end @@ -146,9 +146,24 @@ RSpec.describe BulkImportRowService do end it 'adds the target account to the list' do - expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + expect { subject.call(import_row) }.to add_target_account_to_list end end + + def add_target_account_to_list + change { target_account_on_list? } + .from(false) + .to(true) + end + + def target_account_on_list? + ListAccount + .joins(:list) + .exists?( + account_id: target_account.id, + list: { title: 'my list' } + ) + end end context 'when the list does not exist yet' do diff --git a/spec/support/matchers/api_pagination.rb b/spec/support/matchers/api_pagination.rb new file mode 100644 index 0000000000..81e27e44b8 --- /dev/null +++ b/spec/support/matchers/api_pagination.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :include_pagination_headers do |links| + match do |response| + links.map do |key, value| + response.headers['Link'].find_link(['rel', key.to_s]).href == value + end.all? + end + + failure_message do |header| + "expected that #{header} would have the same values as #{links}." + end +end diff --git a/yarn.lock b/yarn.lock index 65b34467d5..561d974fe8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14534,15 +14534,15 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.71.1 - resolution: "sass@npm:1.71.1" + version: 1.72.0 + resolution: "sass@npm:1.72.0" dependencies: chokidar: "npm:>=3.0.0 <4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10c0/59d79a6e106747746792b0c71908ae0aecdaf9b794d5724ee64e5249412f0d8ebe7ee2bf12946618848f14f949c4f6b530d82da3e62ab31c71198c6f73002130 + checksum: 10c0/7df1bb470648edc4b528976b1b165c78d4c6731f680afac7cdc8324142f1ef4304598d317d98dac747a2ae8eee17271d760def90bba072021a8b19b459336ccd languageName: node linkType: hard