Merge commit '9e0a3aaf08e7d4fc700f7f19d61f5f8fee346b6a' into glitch-soc/merge-upstream
This commit is contained in:
commit
89c2ac444f
@ -210,7 +210,7 @@ FROM media-build AS libvips
|
|||||||
|
|
||||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||||
ARG VIPS_VERSION=8.18.2
|
ARG VIPS_VERSION=8.18.3
|
||||||
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
||||||
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ class CollectionsController < ApplicationController
|
|||||||
|
|
||||||
before_action :require_account_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :set_collection
|
before_action :set_collection
|
||||||
|
before_action :redirect_to_canonical_url
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, only: :show, unless: :limited_federation_mode?
|
skip_before_action :require_functional!, only: :show, unless: :limited_federation_mode?
|
||||||
@ -18,7 +19,6 @@ class CollectionsController < ApplicationController
|
|||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in expiration_duration, public: true unless user_signed_in?
|
expires_in expiration_duration, public: true unless user_signed_in?
|
||||||
render template: 'home/index'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
@ -46,6 +46,10 @@ class CollectionsController < ApplicationController
|
|||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def redirect_to_canonical_url
|
||||||
|
redirect_to collection_path(@collection) if request.format.html? && request.path.starts_with?('/ap/')
|
||||||
|
end
|
||||||
|
|
||||||
def expiration_duration
|
def expiration_duration
|
||||||
recently_updated = @collection.updated_at > 15.minutes.ago
|
recently_updated = @collection.updated_at > 15.minutes.ago
|
||||||
recently_updated ? 30.seconds : 5.minutes
|
recently_updated ? 30.seconds : 5.minutes
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
export const ensureComposeIsVisible = (getState) => {
|
export const ensureComposeIsVisible = (getState) => {
|
||||||
if (!getState().getIn(['compose', 'mounted'])) {
|
if (!getState().getIn(['compose', 'mounted'])) {
|
||||||
browserHistory.push('/publish');
|
browserHistory.push('/publish', { focusTarget: false });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -294,7 +294,10 @@ export function submitCompose(successCallback) {
|
|||||||
message: statusId === null ? messages.published : messages.saved,
|
message: statusId === null ? messages.published : messages.saved,
|
||||||
action: messages.open,
|
action: messages.open,
|
||||||
dismissAfter: 10000,
|
dismissAfter: 10000,
|
||||||
onClick: () => browserHistory.push(`/@${response.data.account.username}/${response.data.id}`),
|
onClick: () => browserHistory.push(
|
||||||
|
`/@${response.data.account.username}/${response.data.id}`,
|
||||||
|
{ focusTarget: 'detailed-status' }
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
dispatch(submitComposeFail(error));
|
dispatch(submitComposeFail(error));
|
||||||
@ -341,11 +344,6 @@ export function uploadCompose(files) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getState().getIn(['compose', 'poll'])) {
|
|
||||||
dispatch(showAlert({ message: messages.uploadErrorPoll }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(uploadComposeRequest());
|
dispatch(uploadComposeRequest());
|
||||||
|
|
||||||
for (const [i, file] of Array.from(files).entries()) {
|
for (const [i, file] of Array.from(files).entries()) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export async function importCustomEmoji(emojis: ApiCustomEmojiJSON[]) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If there's a mismatch, re-import all custom emojis.
|
// If there's a mismatch, re-import all custom emojis.
|
||||||
if (existingEmojis.length < emojis.length) {
|
if (existingEmojis.length > 0 && existingEmojis.length < emojis.length) {
|
||||||
await clearCache('custom');
|
await clearCache('custom');
|
||||||
await loadCustomEmoji();
|
await loadCustomEmoji();
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ interface OpenModalPayload {
|
|||||||
modalType: ModalType;
|
modalType: ModalType;
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
previousModalProps?: ModalProps;
|
previousModalProps?: ModalProps;
|
||||||
|
ignoreFocus?: boolean;
|
||||||
}
|
}
|
||||||
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
|
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { FollowsYouBadge } from '../badge';
|
|||||||
import { CopyButton } from '../copy_button';
|
import { CopyButton } from '../copy_button';
|
||||||
import { DisplayName } from '../display_name';
|
import { DisplayName } from '../display_name';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
|
import { NavigationFocusTarget } from '../navigation_focus_target';
|
||||||
|
|
||||||
import { AccountBadges } from './badges';
|
import { AccountBadges } from './badges';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
@ -56,9 +57,9 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={classes.nameWrapper}>
|
<div className={classes.nameWrapper}>
|
||||||
<div className={classes.name}>
|
<div className={classes.name}>
|
||||||
<h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
<DisplayName account={account} variant='simple' />
|
<DisplayName account={account} variant='simple' />
|
||||||
</h1>
|
</NavigationFocusTarget>
|
||||||
{relationship?.followed_by && <FollowsYouBadge />}
|
{relationship?.followed_by && <FollowsYouBadge />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { useIdentity } from 'mastodon/identity_context';
|
|||||||
import { useColumnIndexContext } from '../features/ui/components/columns_area';
|
import { useColumnIndexContext } from '../features/ui/components/columns_area';
|
||||||
import { getColumnSkipLinkId } from '../features/ui/components/skip_links';
|
import { getColumnSkipLinkId } from '../features/ui/components/skip_links';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from './navigation_focus_target';
|
||||||
import { useAppHistory } from './router';
|
import { useAppHistory } from './router';
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
@ -272,7 +273,7 @@ export const ColumnHeader: React.FC<Props> = ({
|
|||||||
{!backButton && hasIcon && (
|
{!backButton && hasIcon && (
|
||||||
<Icon id={icon} icon={iconComponent} className='column-header__icon' />
|
<Icon id={icon} icon={iconComponent} className='column-header__icon' />
|
||||||
)}
|
)}
|
||||||
{title}
|
<span className='column-header__text'>{title}</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -285,7 +286,10 @@ export const ColumnHeader: React.FC<Props> = ({
|
|||||||
<div className={headingClassName}>
|
<div className={headingClassName}>
|
||||||
{backButton}
|
{backButton}
|
||||||
{hasTitle && (
|
{hasTitle && (
|
||||||
<h1 className='column-header__title-wrapper'>
|
<NavigationFocusTarget
|
||||||
|
as='h1'
|
||||||
|
className='column-header__title-wrapper'
|
||||||
|
>
|
||||||
{onClick ? (
|
{onClick ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleTitleClick}
|
onClick={handleTitleClick}
|
||||||
@ -304,7 +308,7 @@ export const ColumnHeader: React.FC<Props> = ({
|
|||||||
{titleContents}
|
{titleContents}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</NavigationFocusTarget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='column-header__buttons'>
|
<div className='column-header__buttons'>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { multiply } from 'color-blend';
|
|||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
|
import { WithOptionalRouterPropTypes, withOptionalRouter } from 'mastodon/utils/react_router';
|
||||||
|
import { IGNORE_FOCUS_ON_OPEN } from '../reducers/modal';
|
||||||
|
|
||||||
class ModalRoot extends PureComponent {
|
class ModalRoot extends PureComponent {
|
||||||
|
|
||||||
@ -21,7 +22,10 @@ class ModalRoot extends PureComponent {
|
|||||||
b: PropTypes.number,
|
b: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
ignoreFocus: PropTypes.bool,
|
ignoreFocus: PropTypes.oneOfType([
|
||||||
|
PropTypes.bool,
|
||||||
|
PropTypes.string, // 'on-open', see IGNORE_FOCUS_ON_OPEN
|
||||||
|
]),
|
||||||
...WithOptionalRouterPropTypes,
|
...WithOptionalRouterPropTypes,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,7 +122,11 @@ class ModalRoot extends PureComponent {
|
|||||||
_ensureHistoryBuffer () {
|
_ensureHistoryBuffer () {
|
||||||
const { pathname, search, hash, state } = this.history.location;
|
const { pathname, search, hash, state } = this.history.location;
|
||||||
if (!state || state.mastodonModalKey !== this._modalHistoryKey) {
|
if (!state || state.mastodonModalKey !== this._modalHistoryKey) {
|
||||||
this.history.push({ pathname, search, hash }, { ...state, mastodonModalKey: this._modalHistoryKey });
|
this.history.push({ pathname, search, hash }, {
|
||||||
|
...state,
|
||||||
|
focusTarget: this.props.ignoreFocus !== IGNORE_FOCUS_ON_OPEN,
|
||||||
|
mastodonModalKey: this._modalHistoryKey,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,145 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useRef,
|
||||||
|
useLayoutEffect,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||||
|
|
||||||
|
import type { MastodonLocation } from '../router';
|
||||||
|
|
||||||
|
export const FOCUS_TARGET = {
|
||||||
|
POST: 'detailed-status',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type FocusTarget =
|
||||||
|
| boolean
|
||||||
|
| (typeof FOCUS_TARGET)[keyof typeof FOCUS_TARGET];
|
||||||
|
|
||||||
|
const FocusTargetContext =
|
||||||
|
createContext<React.MutableRefObject<FocusTarget> | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `FocusTargetProvider` keeps track of whether focus should be
|
||||||
|
* set after a navigation. By default, any navigation will set the
|
||||||
|
* current value of `focusTargetRef` to `true`, which will cause
|
||||||
|
* the `NavigationFocusTarget` component to focus itself when it mounts.
|
||||||
|
*
|
||||||
|
* To disable this behaviour for a navigation, the focus target can be
|
||||||
|
* set to `false` using location state, for example:
|
||||||
|
* ```
|
||||||
|
* location.push(url, { focusTarget: false });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If the target page contains multiple `NavigationFocusTarget` components
|
||||||
|
* (e.g. a main heading and a post that should be focused), give the more
|
||||||
|
* specific `NavigationFocusTarget` instance a name, and pass the same name
|
||||||
|
* via location state:
|
||||||
|
* ```
|
||||||
|
* location.push(url, { focusTarget: 'detailed-status' });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const FocusTargetProvider: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ children }) => {
|
||||||
|
const focusTargetRef = useRef<FocusTarget>(false);
|
||||||
|
const previousLocationRef = useRef<
|
||||||
|
| (Pick<MastodonLocation, 'pathname' | 'search'> & {
|
||||||
|
focusTarget?: FocusTarget;
|
||||||
|
})
|
||||||
|
| null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
pathname,
|
||||||
|
search,
|
||||||
|
state = {},
|
||||||
|
} = useLocation<{ focusTarget?: FocusTarget } | undefined>();
|
||||||
|
|
||||||
|
const { focusTarget } = state;
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// We never want to set focus on page load, so we keep
|
||||||
|
// track of whether a manual navigation has occurred by comparing
|
||||||
|
// our current with the previous location:
|
||||||
|
const previous = previousLocationRef.current;
|
||||||
|
|
||||||
|
// Bail out on the first render, populate previousLocationRef
|
||||||
|
if (previous === null) {
|
||||||
|
previousLocationRef.current = { pathname, search, focusTarget };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bail out if location hasn't changed
|
||||||
|
if (
|
||||||
|
previous.pathname === pathname &&
|
||||||
|
previous.search === search &&
|
||||||
|
previous.focusTarget === focusTarget
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location has changed:
|
||||||
|
// - Set focusTarget
|
||||||
|
// – Store current location as previous
|
||||||
|
// (We store `focusTarget` as `false` to allow overriding it.)
|
||||||
|
previousLocationRef.current = { pathname, search, focusTarget: false };
|
||||||
|
focusTargetRef.current = focusTarget ?? true;
|
||||||
|
}, [pathname, search, focusTarget]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FocusTargetContext.Provider value={focusTargetRef}>
|
||||||
|
{children}
|
||||||
|
</FocusTargetContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useFocusOnNavigation(targetName?: string) {
|
||||||
|
const focusTargetRef = useContext(FocusTargetContext);
|
||||||
|
|
||||||
|
if (focusTargetRef === null) {
|
||||||
|
throw Error(
|
||||||
|
'useFocusTargetContext must be used inside of a FocusTargetProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(element: HTMLElement | null) => {
|
||||||
|
const focusTarget = focusTargetRef.current;
|
||||||
|
|
||||||
|
// Bail out if focusTarget was set to `false`
|
||||||
|
if (!element || !focusTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusTarget === true || focusTarget === targetName) {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.focus({ preventScroll: true });
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[focusTargetRef, targetName],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FocusTargetElementProps extends React.ComponentPropsWithoutRef<'h1'> {
|
||||||
|
focusTargetName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavigationFocusTarget = polymorphicForwardRef<
|
||||||
|
'h1',
|
||||||
|
FocusTargetElementProps
|
||||||
|
>(({ as: Component = 'h1', focusTargetName, children, ...otherProps }) => {
|
||||||
|
const focusOnNavigation = useFocusOnNavigation(focusTargetName);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component ref={focusOnNavigation} tabIndex={-1} {...otherProps}>
|
||||||
|
{children}
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -14,9 +14,14 @@ import { createBrowserHistory } from 'history';
|
|||||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||||
import { isDevelopment } from 'mastodon/utils/environment';
|
import { isDevelopment } from 'mastodon/utils/environment';
|
||||||
|
|
||||||
|
import type { FocusTarget } from './navigation_focus_target';
|
||||||
|
|
||||||
interface MastodonLocationState {
|
interface MastodonLocationState {
|
||||||
fromMastodon?: boolean;
|
fromMastodon?: boolean;
|
||||||
mastodonModalKey?: string;
|
mastodonModalKey?: string;
|
||||||
|
// Controls which element is focused after a navigation.
|
||||||
|
// Set to `false` to prevent navigation focus.
|
||||||
|
focusTarget?: FocusTarget;
|
||||||
// Prevent the rightmost column in advanced UI from scrolling
|
// Prevent the rightmost column in advanced UI from scrolling
|
||||||
// into view on location changes
|
// into view on location changes
|
||||||
preventMultiColumnAutoScroll?: string;
|
preventMultiColumnAutoScroll?: string;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import StatusContent from './status_content';
|
|||||||
import { StatusThreadLabel } from './status_thread_label';
|
import { StatusThreadLabel } from './status_thread_label';
|
||||||
import { CollectionPreviewCard } from '../features/collections/components/collection_preview_card';
|
import { CollectionPreviewCard } from '../features/collections/components/collection_preview_card';
|
||||||
import { compareUrls } from '../utils/compare_urls';
|
import { compareUrls } from '../utils/compare_urls';
|
||||||
|
import { FOCUS_TARGET } from './navigation_focus_target';
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
@ -311,9 +312,9 @@ class Status extends ImmutablePureComponent {
|
|||||||
window.open(path, '_blank', 'noopener');
|
window.open(path, '_blank', 'noopener');
|
||||||
} else {
|
} else {
|
||||||
if (history.location.pathname.replace('/deck/', '/') === path) {
|
if (history.location.pathname.replace('/deck/', '/') === path) {
|
||||||
history.replace(path);
|
history.replace(path, {focusTarget: FOCUS_TARGET.POST});
|
||||||
} else {
|
} else {
|
||||||
history.push(path);
|
history.push(path, {focusTarget: FOCUS_TARGET.POST});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { Provider as ReduxProvider } from 'react-redux';
|
|||||||
import { hydrateStore } from 'mastodon/actions/store';
|
import { hydrateStore } from 'mastodon/actions/store';
|
||||||
import { connectUserStream } from 'mastodon/actions/streaming';
|
import { connectUserStream } from 'mastodon/actions/streaming';
|
||||||
import ErrorBoundary from 'mastodon/components/error_boundary';
|
import ErrorBoundary from 'mastodon/components/error_boundary';
|
||||||
|
import { FocusTargetProvider } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { Router } from 'mastodon/components/router';
|
import { Router } from 'mastodon/components/router';
|
||||||
import UI from 'mastodon/features/ui';
|
import UI from 'mastodon/features/ui';
|
||||||
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
|
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
|
||||||
@ -49,7 +50,9 @@ export default class Mastodon extends PureComponent {
|
|||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Router>
|
<Router>
|
||||||
<ScrollContext>
|
<ScrollContext>
|
||||||
<Route path='/' component={UI} />
|
<FocusTargetProvider>
|
||||||
|
<Route path='/' component={UI} />
|
||||||
|
</FocusTargetProvider>
|
||||||
</ScrollContext>
|
</ScrollContext>
|
||||||
<BodyScrollLock />
|
<BodyScrollLock />
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@ -8,10 +8,13 @@ import { Helmet } from '@unhead/react/helmet';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { domain } from 'mastodon/initial_state';
|
||||||
|
|
||||||
import { injectIntl } from '@/mastodon/components/intl';
|
import { injectIntl } from '@/mastodon/components/intl';
|
||||||
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
|
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server';
|
||||||
import { Account } from 'mastodon/components/account';
|
import { Account } from 'mastodon/components/account';
|
||||||
import Column from 'mastodon/components/column';
|
import Column from 'mastodon/components/column';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||||
import { Skeleton } from 'mastodon/components/skeleton';
|
import { Skeleton } from 'mastodon/components/skeleton';
|
||||||
import { LinkFooter} from 'mastodon/features/ui/components/link_footer';
|
import { LinkFooter} from 'mastodon/features/ui/components/link_footer';
|
||||||
@ -91,7 +94,9 @@ class About extends PureComponent {
|
|||||||
srcSet={Object.keys(server.item?.thumbnail.versions ?? {}).map((key) => `${server.item?.thumbnail.versions && server.item.thumbnail.versions[key]} ${key.replace('@', '')}`).join(', ')}
|
srcSet={Object.keys(server.item?.thumbnail.versions ?? {}).map((key) => `${server.item?.thumbnail.versions && server.item.thumbnail.versions[key]} ${key.replace('@', '')}`).join(', ')}
|
||||||
className='about__header__hero'
|
className='about__header__hero'
|
||||||
/>
|
/>
|
||||||
<h1>{isLoading ? <Skeleton width='10ch' /> : server.domain}</h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
{isLoading ? <Skeleton width='10ch' /> : domain}
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank' rel='noopener'>Mastodon</a> }} /></p>
|
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank' rel='noopener'>Mastodon</a> }} /></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export const AccountFieldActions: FC<{ id: string }> = ({ id }) => {
|
|||||||
openModal({
|
openModal({
|
||||||
modalType: 'ACCOUNT_EDIT_FIELD_EDIT',
|
modalType: 'ACCOUNT_EDIT_FIELD_EDIT',
|
||||||
modalProps: { fieldKey: id },
|
modalProps: { fieldKey: id },
|
||||||
|
ignoreFocus: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|||||||
@ -137,16 +137,28 @@ export const AccountEdit: FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenModal = useCallback(
|
const handleOpenModal = useCallback(
|
||||||
(type: ModalType, props?: Record<string, unknown>) => {
|
(
|
||||||
dispatch(openModal({ modalType: type, modalProps: props ?? {} }));
|
type: ModalType,
|
||||||
|
{
|
||||||
|
modalProps = {},
|
||||||
|
ignoreFocus = false,
|
||||||
|
}: { modalProps?: Record<string, unknown>; ignoreFocus?: boolean } = {},
|
||||||
|
) => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: type,
|
||||||
|
modalProps,
|
||||||
|
ignoreFocus,
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
const handleNameEdit = useCallback(() => {
|
const handleNameEdit = useCallback(() => {
|
||||||
handleOpenModal('ACCOUNT_EDIT_NAME');
|
handleOpenModal('ACCOUNT_EDIT_NAME', { ignoreFocus: true });
|
||||||
}, [handleOpenModal]);
|
}, [handleOpenModal]);
|
||||||
const handleBioEdit = useCallback(() => {
|
const handleBioEdit = useCallback(() => {
|
||||||
handleOpenModal('ACCOUNT_EDIT_BIO');
|
handleOpenModal('ACCOUNT_EDIT_BIO', { ignoreFocus: true });
|
||||||
}, [handleOpenModal]);
|
}, [handleOpenModal]);
|
||||||
const handleCustomFieldAdd = useCallback(() => {
|
const handleCustomFieldAdd = useCallback(() => {
|
||||||
handleOpenModal('ACCOUNT_EDIT_FIELD_EDIT');
|
handleOpenModal('ACCOUNT_EDIT_FIELD_EDIT');
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
ModalShellActions,
|
ModalShellActions,
|
||||||
ModalShellBody,
|
ModalShellBody,
|
||||||
} from '@/mastodon/components/modal_shell';
|
} from '@/mastodon/components/modal_shell';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
import { useFieldHtml } from '../hooks/useFieldHtml';
|
import { useFieldHtml } from '../hooks/useFieldHtml';
|
||||||
|
|
||||||
@ -24,12 +25,14 @@ export const AccountFieldModal: FC<{
|
|||||||
return (
|
return (
|
||||||
<ModalShell>
|
<ModalShell>
|
||||||
<ModalShellBody>
|
<ModalShellBody>
|
||||||
<EmojiHTML
|
<NavigationFocusTarget as='h1'>
|
||||||
as='h2'
|
<EmojiHTML
|
||||||
htmlString={field.name_emojified}
|
as='span'
|
||||||
onElement={handleLabelElement}
|
htmlString={field.name_emojified}
|
||||||
className={classes.fieldName}
|
onElement={handleLabelElement}
|
||||||
/>
|
className={classes.fieldName}
|
||||||
|
/>
|
||||||
|
</NavigationFocusTarget>
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
as='p'
|
as='p'
|
||||||
htmlString={field.value_emojified}
|
htmlString={field.value_emojified}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { List, Map } from 'immutable';
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
import { FocusTargetProvider } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
import { Router } from '@/mastodon/components/router';
|
||||||
import type { RootState } from 'mastodon/store';
|
import type { RootState } from 'mastodon/store';
|
||||||
import { useAppSelector } from 'mastodon/store';
|
import { useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
@ -27,9 +29,13 @@ describe('<AltTextModal />', () => {
|
|||||||
|
|
||||||
const renderComponent = () => {
|
const renderComponent = () => {
|
||||||
return render(
|
return render(
|
||||||
<IntlProvider locale='en' messages={{}}>
|
<Router>
|
||||||
<AltTextModal mediaId={mediaId} onClose={handleClose} />
|
<FocusTargetProvider>
|
||||||
</IntlProvider>,
|
<IntlProvider locale='en' messages={{}}>
|
||||||
|
<AltTextModal mediaId={mediaId} onClose={handleClose} />
|
||||||
|
</IntlProvider>
|
||||||
|
</FocusTargetProvider>
|
||||||
|
</Router>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { changeUploadCompose } from 'mastodon/actions/compose_typed';
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { GIFV } from 'mastodon/components/gifv';
|
import { GIFV } from 'mastodon/components/gifv';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { Skeleton } from 'mastodon/components/skeleton';
|
import { Skeleton } from 'mastodon/components/skeleton';
|
||||||
import { Audio } from 'mastodon/features/audio';
|
import { Audio } from 'mastodon/features/audio';
|
||||||
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
||||||
@ -412,12 +413,15 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<span className='dialog-modal__header__title'>
|
<NavigationFocusTarget
|
||||||
|
as='h1'
|
||||||
|
className='dialog-modal__header__title'
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='alt_text_modal.add_alt_text'
|
id='alt_text_modal.add_alt_text'
|
||||||
defaultMessage='Add alt text'
|
defaultMessage='Add alt text'
|
||||||
/>
|
/>
|
||||||
</span>
|
</NavigationFocusTarget>
|
||||||
|
|
||||||
<Button secondary onClick={onClose}>
|
<Button secondary onClick={onClose}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import classNames from 'classnames/bind';
|
|||||||
import { closeModal } from '@/mastodon/actions/modal';
|
import { closeModal } from '@/mastodon/actions/modal';
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { getReport } from '@/mastodon/reducers/slices/annual_report';
|
import { getReport } from '@/mastodon/reducers/slices/annual_report';
|
||||||
import {
|
import {
|
||||||
createAppSelector,
|
createAppSelector,
|
||||||
@ -83,7 +84,9 @@ export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.wrapper} data-color-scheme='dark'>
|
<div className={styles.wrapper} data-color-scheme='dark'>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h1>Wrapstodon {report.year}</h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
Wrapstodon {report.year}
|
||||||
|
</NavigationFocusTarget>
|
||||||
{account && <p>@{account.acct}</p>}
|
{account && <p>@{account.acct}</p>}
|
||||||
{context === 'modal' && (
|
{context === 'modal' && (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||||||
|
|
||||||
import { fetchServer } from 'mastodon/actions/server';
|
import { fetchServer } from 'mastodon/actions/server';
|
||||||
import { domain } from 'mastodon/initial_state';
|
import { domain } from 'mastodon/initial_state';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
message: state.getIn(['server', 'server', 'item', 'registrations', 'message']),
|
message: state.getIn(['server', 'server', 'item', 'registrations', 'message']),
|
||||||
@ -42,7 +43,9 @@ class ClosedRegistrationsModal extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='modal-root__modal interaction-modal'>
|
<div className='modal-root__modal interaction-modal'>
|
||||||
<div className='interaction-modal__lead'>
|
<div className='interaction-modal__lead'>
|
||||||
<h3><FormattedMessage id='closed_registrations_modal.title' defaultMessage='Signing up on Mastodon' /></h3>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
<FormattedMessage id='closed_registrations_modal.title' defaultMessage='Signing up on Mastodon' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='closed_registrations_modal.preamble'
|
id='closed_registrations_modal.preamble'
|
||||||
@ -53,12 +56,12 @@ class ClosedRegistrationsModal extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<div className='interaction-modal__choices'>
|
<div className='interaction-modal__choices'>
|
||||||
<div className='interaction-modal__choices__choice'>
|
<div className='interaction-modal__choices__choice'>
|
||||||
<h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
|
<h2><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h2>
|
||||||
{closedRegistrationsMessage}
|
{closedRegistrationsMessage}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='interaction-modal__choices__choice'>
|
<div className='interaction-modal__choices__choice'>
|
||||||
<h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
|
<h2><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h2>
|
||||||
<p className='prose'>
|
<p className='prose'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='closed_registrations.other_server_instructions'
|
id='closed_registrations.other_server_instructions'
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
|||||||
|
|
||||||
import type { ApiCollectionJSON } from '@/mastodon/api_types/collections';
|
import type { ApiCollectionJSON } from '@/mastodon/api_types/collections';
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
import type { Account } from '@/mastodon/models/account';
|
import type { Account } from '@/mastodon/models/account';
|
||||||
import {
|
import {
|
||||||
@ -114,13 +115,17 @@ export const CollectionAdder: React.FC<{
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span className='dialog-modal__header__title' id={titleId}>
|
<NavigationFocusTarget
|
||||||
|
as='h1'
|
||||||
|
id={titleId}
|
||||||
|
className='dialog-modal__header__title'
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collections.add_to_collection'
|
id='collections.add_to_collection'
|
||||||
defaultMessage='Add {name} to collections'
|
defaultMessage='Add {name} to collections'
|
||||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||||
/>
|
/>
|
||||||
</span>
|
</NavigationFocusTarget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='dialog-modal__content'>
|
<div className='dialog-modal__content'>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import { useLocation } from 'react-router';
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { me } from '@/mastodon/initial_state';
|
import { me } from '@/mastodon/initial_state';
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
import { changeCompose, focusCompose } from 'mastodon/actions/compose';
|
import { changeCompose, focusCompose } from 'mastodon/actions/compose';
|
||||||
@ -66,7 +67,7 @@ export const CollectionShareModal: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<ModalShell>
|
<ModalShell>
|
||||||
<ModalShellBody>
|
<ModalShellBody>
|
||||||
<h1 className={classes.heading}>
|
<NavigationFocusTarget as='h1' className={classes.heading}>
|
||||||
{isNew ? (
|
{isNew ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collection.share_modal.title_new'
|
id='collection.share_modal.title_new'
|
||||||
@ -78,7 +79,7 @@ export const CollectionShareModal: React.FC<{
|
|||||||
defaultMessage='Share collection'
|
defaultMessage='Share collection'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</NavigationFocusTarget>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Helmet } from '@unhead/react/helmet';
|
import { Helmet } from '@unhead/react/helmet';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { DisplayNameSimple } from 'mastodon/components/display_name/simple';
|
import { DisplayNameSimple } from 'mastodon/components/display_name/simple';
|
||||||
@ -71,7 +72,9 @@ export const Collections: React.FC<{
|
|||||||
|
|
||||||
<Scrollable>
|
<Scrollable>
|
||||||
<header className={classes.header}>
|
<header className={classes.header}>
|
||||||
<h1 className={classes.heading}>{pageTitleHtml}</h1>
|
<NavigationFocusTarget as='h1' className={classes.heading}>
|
||||||
|
{pageTitleHtml}
|
||||||
|
</NavigationFocusTarget>
|
||||||
<TabList plain>
|
<TabList plain>
|
||||||
<TabLink exact to={`/@${account?.acct}/collections`}>
|
<TabLink exact to={`/@${account?.acct}/collections`}>
|
||||||
{intl.formatMessage(createdByTabMessage, {
|
{intl.formatMessage(createdByTabMessage, {
|
||||||
|
|||||||
@ -220,7 +220,9 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
||||||
this.textareaRef.current.focus();
|
this.textareaRef.current.focus();
|
||||||
} else if (this.props.spoiler !== prevProps.spoiler) {
|
} else if (this.props.spoiler !== prevProps.spoiler) {
|
||||||
if (this.props.spoiler) {
|
const mediaJustAdded = this.props.anyMedia && !prevProps.anyMedia;
|
||||||
|
|
||||||
|
if (this.props.spoiler && !mediaJustAdded) {
|
||||||
this.spoilerText.input.focus();
|
this.spoilerText.input.focus();
|
||||||
} else if (prevProps.spoiler) {
|
} else if (prevProps.spoiler) {
|
||||||
this.textareaRef.current.focus();
|
this.textareaRef.current.focus();
|
||||||
|
|||||||
@ -4,12 +4,10 @@ import { addPoll, removePoll } from '../../../actions/compose';
|
|||||||
import PollButton from '../components/poll_button';
|
import PollButton from '../components/poll_button';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const readyAttachmentsSize = state.compose.get('media_attachments').size ?? 0;
|
|
||||||
const hasAttachments = readyAttachmentsSize > 0 || !!state.compose.get('is_uploading');
|
|
||||||
const hasQuote = !!state.compose.get('quoted_status_id');
|
const hasQuote = !!state.compose.get('quoted_status_id');
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
disabled: hasAttachments || hasQuote,
|
disabled: hasQuote,
|
||||||
active: state.getIn(['compose', 'poll']) !== null,
|
active: state.getIn(['compose', 'poll']) !== null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { uploadCompose } from '../../../actions/compose';
|
|||||||
import UploadButton from '../components/upload_button';
|
import UploadButton from '../components/upload_button';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const isPoll = state.getIn(['compose', 'poll']) !== null;
|
|
||||||
const isUploading = state.getIn(['compose', 'is_uploading']);
|
const isUploading = state.getIn(['compose', 'is_uploading']);
|
||||||
const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0;
|
const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0;
|
||||||
const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0;
|
const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0;
|
||||||
@ -14,7 +13,7 @@ const mapStateToProps = state => {
|
|||||||
const hasQuote = !!state.compose.get('quoted_status_id');
|
const hasQuote = !!state.compose.get('quoted_status_id');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
disabled: isPoll || isUploading || isOverLimit || hasVideoOrAudio || hasQuote,
|
disabled: isUploading || isOverLimit || hasVideoOrAudio || hasQuote,
|
||||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Helmet } from '@unhead/react/helmet';
|
import { Helmet } from '@unhead/react/helmet';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { fetchServer } from 'mastodon/actions/server';
|
import { fetchServer } from 'mastodon/actions/server';
|
||||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||||
import { TabLink, TabList } from 'mastodon/components/tab_list';
|
import { TabLink, TabList } from 'mastodon/components/tab_list';
|
||||||
@ -40,7 +41,9 @@ export const CustomHomepage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={classes.topSection}>
|
<div className={classes.topSection}>
|
||||||
<h1>{server.item?.domain}</h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
{server.item?.domain}
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p>{server.item?.description}</p>
|
<p>{server.item?.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,10 @@ function rawEmojiFactory(data: Partial<CompactEmoji> = {}): CompactEmoji {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('emoji database', () => {
|
describe('emoji database', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await testGet(); // Loads the database schema.
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
testClear();
|
testClear();
|
||||||
indexedDB = new IDBFactory();
|
indexedDB = new IDBFactory();
|
||||||
|
|||||||
@ -143,7 +143,28 @@ export async function search({
|
|||||||
return intersection;
|
return intersection;
|
||||||
})
|
})
|
||||||
.values(),
|
.values(),
|
||||||
).toSorted((a, b) => a.score - b.score);
|
);
|
||||||
|
|
||||||
|
// If there are no results, try a cursor-based custom emoji search instead.
|
||||||
|
if (results.length === 0) {
|
||||||
|
const trx = db.transaction('custom', 'readonly');
|
||||||
|
const foundEmojis = new Set<string>();
|
||||||
|
for await (const cursor of trx.store) {
|
||||||
|
const emoji = cursor.value;
|
||||||
|
const score = getScoreForEmoji(emoji, query, false);
|
||||||
|
if (score === null || foundEmojis.has(emoji.shortcode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({ ...emoji, score });
|
||||||
|
foundEmojis.add(emoji.shortcode);
|
||||||
|
}
|
||||||
|
log('cursor search found %d results for "%s"', foundEmojis.size, query);
|
||||||
|
await trx.done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by score, descending.
|
||||||
|
results.sort((a, b) => a.score - b.score);
|
||||||
|
|
||||||
const time = performance.measure('emoji-search-end', 'emoji-search-start');
|
const time = performance.measure('emoji-search-end', 'emoji-search-start');
|
||||||
log(
|
log(
|
||||||
@ -159,14 +180,19 @@ export async function search({
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScoreForEmoji(emoji: AnyEmojiData, query: string) {
|
function getScoreForEmoji(
|
||||||
|
emoji: AnyEmojiData,
|
||||||
|
query: string,
|
||||||
|
checkTokens = true,
|
||||||
|
) {
|
||||||
const id = 'shortcode' in emoji ? emoji.shortcode : emoji.label;
|
const id = 'shortcode' in emoji ? emoji.shortcode : emoji.label;
|
||||||
if (id === query) {
|
if (id === query) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
for (const token of [id, ...emoji.tokens]) {
|
const searchTokens = checkTokens ? [id, ...emoji.tokens] : [id];
|
||||||
|
for (const token of searchTokens) {
|
||||||
const tokenIndex = token.indexOf(query);
|
const tokenIndex = token.indexOf(query);
|
||||||
if (tokenIndex !== -1) {
|
if (tokenIndex !== -1) {
|
||||||
return index + tokenIndex / token.length;
|
return index + tokenIndex / token.length;
|
||||||
@ -246,6 +272,13 @@ export async function clearCache(key: CacheKey) {
|
|||||||
log('Cleared cache for %s', key);
|
log('Cleared cache for %s', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resetDatabase() {
|
||||||
|
const db = await loadDB();
|
||||||
|
const storeNames = [...db.objectStoreNames];
|
||||||
|
await Promise.all(storeNames.map((storeName) => db.clear(storeName)));
|
||||||
|
log(storeNames, 'Reset emoji database stores:');
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadEmojiByHexcode(
|
export async function loadEmojiByHexcode(
|
||||||
hexcode: string,
|
hexcode: string,
|
||||||
localeString: string,
|
localeString: string,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
StoreNames,
|
StoreNames,
|
||||||
} from 'idb';
|
} from 'idb';
|
||||||
|
|
||||||
|
import { resetDatabase } from './database';
|
||||||
import type { CustomEmojiData, CacheKey, UnicodeEmojiData } from './types';
|
import type { CustomEmojiData, CacheKey, UnicodeEmojiData } from './types';
|
||||||
import { emojiLogger } from './utils';
|
import { emojiLogger } from './utils';
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ type Transaction<Mode extends IDBTransactionMode = 'versionchange'> =
|
|||||||
|
|
||||||
export type Database = IDBPDatabase<EmojiDB>;
|
export type Database = IDBPDatabase<EmojiDB>;
|
||||||
|
|
||||||
const SCHEMA_VERSION = 3;
|
const SCHEMA_VERSION = 4;
|
||||||
|
|
||||||
export async function openEmojiDB() {
|
export async function openEmojiDB() {
|
||||||
const db = await openDB<EmojiDB>('mastodon-emoji', SCHEMA_VERSION, {
|
const db = await openDB<EmojiDB>('mastodon-emoji', SCHEMA_VERSION, {
|
||||||
@ -98,6 +99,8 @@ export async function openEmojiDB() {
|
|||||||
});
|
});
|
||||||
deleteOldIndexes(shortcodeTable, ['hexcode']);
|
deleteOldIndexes(shortcodeTable, ['hexcode']);
|
||||||
|
|
||||||
|
void resetDatabase();
|
||||||
|
|
||||||
log(
|
log(
|
||||||
'Upgraded emoji database from version %d to %d',
|
'Upgraded emoji database from version %d to %d',
|
||||||
oldVersion,
|
oldVersion,
|
||||||
|
|||||||
@ -57,6 +57,8 @@ export const Emoji: FC<EmojiProps> = ({
|
|||||||
const { mode } = useEmojiAppState();
|
const { mode } = useEmojiAppState();
|
||||||
return (
|
return (
|
||||||
<EmojiRaw
|
<EmojiRaw
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- In React props are frozen, but the library we use is old so doesn't respect that.
|
||||||
|
{...(EmojiRaw.defaultProps ?? {})}
|
||||||
data={EmojiData}
|
data={EmojiData}
|
||||||
set={set}
|
set={set}
|
||||||
sheetSize={sheetSize}
|
sheetSize={sheetSize}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { initialState } from '@/mastodon/initial_state';
|
import { initialState } from '@/mastodon/initial_state';
|
||||||
|
|
||||||
import type { EMOJI_DB_NAME_SHORTCODES } from './constants';
|
|
||||||
import { toSupportedLocale } from './locale';
|
import { toSupportedLocale } from './locale';
|
||||||
import { reloadCustomEmojis } from './picker';
|
import { reloadCustomEmojis } from './picker';
|
||||||
import type { LocaleOrCustom } from './types';
|
import type { EmojiWorkerMessage } from './types';
|
||||||
import { emojiLogger } from './utils';
|
import { emojiLogger } from './utils';
|
||||||
|
|
||||||
const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
||||||
@ -11,9 +10,10 @@ const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
|
|||||||
let worker: Worker | null = null;
|
let worker: Worker | null = null;
|
||||||
|
|
||||||
const log = emojiLogger('index');
|
const log = emojiLogger('index');
|
||||||
|
const workerLog = emojiLogger('worker');
|
||||||
|
|
||||||
// This is too short, but better to fallback quickly than wait.
|
// This is too short, but better to fallback quickly than wait.
|
||||||
const WORKER_TIMEOUT = 1_000;
|
const WORKER_TIMEOUT = 2_000;
|
||||||
|
|
||||||
export async function initializeEmoji() {
|
export async function initializeEmoji() {
|
||||||
log('initializing emojis');
|
log('initializing emojis');
|
||||||
@ -43,43 +43,44 @@ export async function initializeEmoji() {
|
|||||||
const { data: message } = event;
|
const { data: message } = event;
|
||||||
|
|
||||||
worker ??= tempWorker;
|
worker ??= tempWorker;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (message === 'ready') {
|
if (message !== 'ready') {
|
||||||
log('worker ready, loading data');
|
workerLog(message);
|
||||||
clearTimeout(timeoutId);
|
return;
|
||||||
messageWorker('shortcodes');
|
|
||||||
void loadCustomEmoji();
|
|
||||||
void loadEmojiLocale(userLocale);
|
|
||||||
} else {
|
|
||||||
log('got worker message: %s', message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debugValue = localStorage.getItem('debug');
|
||||||
|
if (debugValue) {
|
||||||
|
messageWorker({ type: 'debug', debugValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
workerLog('loading data');
|
||||||
|
messageWorker(userLocale);
|
||||||
|
messageWorker('custom');
|
||||||
|
messageWorker('shortcodes');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fallbackLoad() {
|
async function fallbackLoad() {
|
||||||
log('falling back to main thread for loading');
|
log('falling back to main thread for loading');
|
||||||
|
|
||||||
await loadCustomEmoji();
|
const { importCustomEmojiData, importLegacyShortcodes, importEmojiData } =
|
||||||
const { importLegacyShortcodes } = await import('./loader');
|
await import('./loader');
|
||||||
|
|
||||||
|
const customEmojis = await importCustomEmojiData();
|
||||||
|
if (customEmojis && customEmojis.length > 0) {
|
||||||
|
log('loaded %d custom emojis', customEmojis.length);
|
||||||
|
await reloadCustomEmojis();
|
||||||
|
}
|
||||||
const shortcodes = await importLegacyShortcodes();
|
const shortcodes = await importLegacyShortcodes();
|
||||||
if (shortcodes?.length) {
|
if (shortcodes?.length) {
|
||||||
log('loaded %d legacy shortcodes', shortcodes.length);
|
log('loaded %d legacy shortcodes', shortcodes.length);
|
||||||
}
|
}
|
||||||
await loadEmojiLocale(userLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadEmojiLocale(localeString: string) {
|
const emojis = await importEmojiData(userLocale);
|
||||||
const locale = toSupportedLocale(localeString);
|
if (emojis) {
|
||||||
const { importEmojiData } = await import('./loader');
|
log('loaded %d emojis to locale %s', emojis.length, userLocale);
|
||||||
|
|
||||||
if (worker) {
|
|
||||||
log('asking worker to load locale %s', locale);
|
|
||||||
messageWorker(locale);
|
|
||||||
} else {
|
|
||||||
const emojis = await importEmojiData(locale);
|
|
||||||
if (emojis) {
|
|
||||||
log('loaded %d emojis to locale %s', emojis.length, locale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,11 +97,16 @@ export async function loadCustomEmoji() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function messageWorker(
|
function messageWorker(data: EmojiWorkerMessage | string) {
|
||||||
locale: LocaleOrCustom | typeof EMOJI_DB_NAME_SHORTCODES,
|
|
||||||
) {
|
|
||||||
if (!worker) {
|
if (!worker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
worker.postMessage({ locale });
|
if (typeof data === 'string') {
|
||||||
|
worker.postMessage({
|
||||||
|
type: 'load',
|
||||||
|
storeName: data,
|
||||||
|
} satisfies EmojiWorkerMessage);
|
||||||
|
} else {
|
||||||
|
worker.postMessage(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { basename, resolve } from 'path';
|
|||||||
import { flattenEmojiData } from 'emojibase';
|
import { flattenEmojiData } from 'emojibase';
|
||||||
import unicodeRawEmojis from 'emojibase-data/en/data.json';
|
import unicodeRawEmojis from 'emojibase-data/en/data.json';
|
||||||
|
|
||||||
import { unicodeToTwemojiHex } from './normalize';
|
import { extractTokens, unicodeToTwemojiHex } from './normalize';
|
||||||
|
|
||||||
const emojiSVGFiles = await readdir(
|
const emojiSVGFiles = await readdir(
|
||||||
// This assumes tests are run from project root
|
// This assumes tests are run from project root
|
||||||
@ -33,3 +33,32 @@ describe('unicodeToTwemojiHex', () => {
|
|||||||
expect(svgFileNamesWithoutBorder).toContain(result);
|
expect(svgFileNamesWithoutBorder).toContain(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('extractTokens', () => {
|
||||||
|
test('returns an empty array for blank input', () => {
|
||||||
|
expect(extractTokens(' ', null)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('check token word breaking with Intl.Segmenter', () => {
|
||||||
|
const segmenter = new Intl.Segmenter('en', { granularity: 'word' });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
extractTokens('thumbs_up smiling-face camelCase', segmenter),
|
||||||
|
).toEqual(['thumbs', 'up', 'smiling', 'face', 'camel', 'case']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('check token word breaking with regex', () => {
|
||||||
|
expect(extractTokens('Smile_face joy-test A ok 7 z', null)).toEqual([
|
||||||
|
'smile',
|
||||||
|
'face',
|
||||||
|
'joy',
|
||||||
|
'test',
|
||||||
|
'ok',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ensure +1 and -1 are preserved', () => {
|
||||||
|
expect(extractTokens('+1', null)).toEqual(['+1']);
|
||||||
|
expect(extractTokens('-1', null)).toEqual(['-1']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -214,6 +214,11 @@ export function extractTokens(
|
|||||||
}
|
}
|
||||||
const tokens: string[] = [];
|
const tokens: string[] = [];
|
||||||
|
|
||||||
|
// Handle the edge case of thumbs up and down emoticons.
|
||||||
|
if (input === '+1' || input === '-1') {
|
||||||
|
return [input];
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer to use Intl.Segmenter if available for better locale support.
|
// Prefer to use Intl.Segmenter if available for better locale support.
|
||||||
if (segmenter) {
|
if (segmenter) {
|
||||||
for (const { isWordLike, segment } of segmenter.segment(
|
for (const { isWordLike, segment } of segmenter.segment(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import type { CategoryName, CustomEmoji } from 'emoji-mart';
|
import type { CategoryName, CustomEmoji } from 'emoji-mart';
|
||||||
|
|
||||||
import { autoPlayGif } from '@/mastodon/initial_state';
|
import { autoPlayGif } from '@/mastodon/initial_state';
|
||||||
|
import { createLimitedCache } from '@/mastodon/utils/cache';
|
||||||
|
|
||||||
import { emojiLogger } from './utils';
|
import { emojiLogger } from './utils';
|
||||||
|
|
||||||
@ -21,6 +22,8 @@ let customCategories = [
|
|||||||
'flags',
|
'flags',
|
||||||
] as CategoryName[];
|
] as CategoryName[];
|
||||||
|
|
||||||
|
const searchCache = createLimitedCache<LegacyEmoji[]>({ maxSize: 10, log });
|
||||||
|
|
||||||
export async function fetchCustomEmojiData() {
|
export async function fetchCustomEmojiData() {
|
||||||
if (customEmojis !== null) {
|
if (customEmojis !== null) {
|
||||||
return customEmojis;
|
return customEmojis;
|
||||||
@ -89,6 +92,7 @@ export async function reloadCustomEmojis() {
|
|||||||
await import('@/mastodon/hooks/useCustomEmojis');
|
await import('@/mastodon/hooks/useCustomEmojis');
|
||||||
|
|
||||||
await Promise.all([fetchCustomEmojiData(), loadEmojisIntoCache()]);
|
await Promise.all([fetchCustomEmojiData(), loadEmojisIntoCache()]);
|
||||||
|
searchCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replicates the old legacy search function.
|
// Replicates the old legacy search function.
|
||||||
@ -102,16 +106,25 @@ export async function emojiMartSearch(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheKey = `${query}|${locale}|${limit}`;
|
||||||
|
const cachedResult = searchCache.get(cacheKey);
|
||||||
|
if (cachedResult) {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
const { search } = await import('./database');
|
const { search } = await import('./database');
|
||||||
const results = await search({ query, locale, limit });
|
const results = await search({ query, locale, limit });
|
||||||
return results.map((emoji) =>
|
const legacyResults = results.map((emoji) =>
|
||||||
'shortcode' in emoji
|
'shortcode' in emoji
|
||||||
? { id: emoji.shortcode, custom: true }
|
? ({ id: emoji.shortcode, custom: true } as const)
|
||||||
: {
|
: {
|
||||||
id: emoji.label.replaceAll(' ', '_').toLowerCase(),
|
id: emoji.label.replaceAll(' ', '_').toLowerCase(),
|
||||||
native: emoji.unicode,
|
native: emoji.unicode,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
searchCache.set(cacheKey, legacyResults);
|
||||||
|
|
||||||
|
return legacyResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePickerEmojis() {
|
export function usePickerEmojis() {
|
||||||
|
|||||||
@ -80,3 +80,13 @@ export type ExtraCustomEmojiMap = Record<
|
|||||||
string,
|
string,
|
||||||
Pick<CustomEmojiData, 'shortcode' | 'static_url' | 'url'>
|
Pick<CustomEmojiData, 'shortcode' | 'static_url' | 'url'>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type EmojiWorkerMessage =
|
||||||
|
| {
|
||||||
|
type: 'load';
|
||||||
|
storeName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'debug';
|
||||||
|
debugValue: string;
|
||||||
|
};
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import { emojiRegexPolyfill } from '@/mastodon/polyfills';
|
|||||||
import { VARIATION_SELECTOR_CODE } from './constants';
|
import { VARIATION_SELECTOR_CODE } from './constants';
|
||||||
|
|
||||||
export function emojiLogger(segment: string) {
|
export function emojiLogger(segment: string) {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return debug(`emojis:worker:${segment}`);
|
||||||
|
}
|
||||||
return debug(`emojis:${segment}`);
|
return debug(`emojis:${segment}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,31 +1,36 @@
|
|||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants';
|
import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants';
|
||||||
import {
|
import {
|
||||||
importCustomEmojiData,
|
importCustomEmojiData,
|
||||||
importEmojiData,
|
importEmojiData,
|
||||||
importLegacyShortcodes,
|
importLegacyShortcodes,
|
||||||
} from './loader';
|
} from './loader';
|
||||||
|
import type { EmojiWorkerMessage } from './types';
|
||||||
|
|
||||||
addEventListener('message', handleMessage);
|
addEventListener('message', handleMessage);
|
||||||
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
||||||
|
|
||||||
function handleMessage(event: MessageEvent<{ locale: string }>) {
|
function handleMessage(event: MessageEvent<EmojiWorkerMessage>) {
|
||||||
const {
|
const { data } = event;
|
||||||
data: { locale },
|
if (data.type === 'debug') {
|
||||||
} = event;
|
debug.enable(data.debugValue);
|
||||||
void loadData(locale);
|
} else {
|
||||||
|
void loadData(data.storeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadData(locale: string) {
|
async function loadData(storeName: string) {
|
||||||
let importCount: number | undefined;
|
let importCount: number | undefined;
|
||||||
if (locale === EMOJI_TYPE_CUSTOM) {
|
if (storeName === EMOJI_TYPE_CUSTOM) {
|
||||||
importCount = (await importCustomEmojiData())?.length;
|
importCount = (await importCustomEmojiData())?.length;
|
||||||
} else if (locale === EMOJI_DB_NAME_SHORTCODES) {
|
} else if (storeName === EMOJI_DB_NAME_SHORTCODES) {
|
||||||
importCount = (await importLegacyShortcodes())?.length;
|
importCount = (await importLegacyShortcodes())?.length;
|
||||||
} else {
|
} else {
|
||||||
importCount = (await importEmojiData(locale))?.length;
|
importCount = (await importEmojiData(storeName))?.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importCount) {
|
if (importCount) {
|
||||||
self.postMessage(`loaded ${importCount} emojis into ${locale}`);
|
self.postMessage(`loaded ${importCount} emojis into ${storeName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { toServerSideType } from 'mastodon/utils/filters';
|
import { toServerSideType } from 'mastodon/utils/filters';
|
||||||
|
|
||||||
const mapStateToProps = (state, { filterId }) => ({
|
const mapStateToProps = (state, { filterId }) => ({
|
||||||
@ -71,7 +72,9 @@ class AddedToFilter extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='filter_modal.added.title' defaultMessage='Filter added!' /></h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
<FormattedMessage id='filter_modal.added.title' defaultMessage='Filter added!' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'>
|
<p className='report-dialog-modal__lead'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='filter_modal.added.short_explanation'
|
id='filter_modal.added.short_explanation'
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import fuzzysort from 'fuzzysort';
|
|||||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { injectIntl } from '@/mastodon/components/intl';
|
import { injectIntl } from '@/mastodon/components/intl';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { toServerSideType } from 'mastodon/utils/filters';
|
import { toServerSideType } from 'mastodon/utils/filters';
|
||||||
import { loupeIcon, deleteIcon } from 'mastodon/utils/icons';
|
import { loupeIcon, deleteIcon } from 'mastodon/utils/icons';
|
||||||
|
|
||||||
@ -176,7 +177,9 @@ class SelectFilter extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='filter_modal.select_filter.title' defaultMessage='Filter this post' /></h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
<FormattedMessage id='filter_modal.select_filter.title' defaultMessage='Filter this post' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'><FormattedMessage id='filter_modal.select_filter.subtitle' defaultMessage='Use an existing category or create a new one' /></p>
|
<p className='report-dialog-modal__lead'><FormattedMessage id='filter_modal.select_filter.subtitle' defaultMessage='Use an existing category or create a new one' /></p>
|
||||||
|
|
||||||
<div className='emoji-mart-search'>
|
<div className='emoji-mart-search'>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { escapeRegExp } from 'lodash';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import { DisplayName } from '@/mastodon/components/display_name';
|
import { DisplayName } from '@/mastodon/components/display_name';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { openModal, closeModal } from 'mastodon/actions/modal';
|
import { openModal, closeModal } from 'mastodon/actions/modal';
|
||||||
import { apiRequest } from 'mastodon/api';
|
import { apiRequest } from 'mastodon/api';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
@ -474,12 +475,12 @@ const InteractionModal: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<div className='modal-root__modal interaction-modal'>
|
<div className='modal-root__modal interaction-modal'>
|
||||||
<div className='interaction-modal__lead'>
|
<div className='interaction-modal__lead'>
|
||||||
<h3>
|
<NavigationFocusTarget as='h1'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='interaction_modal.title'
|
id='interaction_modal.title'
|
||||||
defaultMessage='Sign in to continue'
|
defaultMessage='Sign in to continue'
|
||||||
/>
|
/>
|
||||||
</h3>
|
</NavigationFocusTarget>
|
||||||
<p>
|
<p>
|
||||||
{intent === 'follow' ? (
|
{intent === 'follow' ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import type { ApiListJSON } from 'mastodon/api_types/lists';
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { getOrderedLists } from 'mastodon/selectors/lists';
|
import { getOrderedLists } from 'mastodon/selectors/lists';
|
||||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
@ -182,13 +183,13 @@ const ListAdder: React.FC<{
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span className='dialog-modal__header__title'>
|
<NavigationFocusTarget as='h1' className='dialog-modal__header__title'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='lists.add_to_lists'
|
id='lists.add_to_lists'
|
||||||
defaultMessage='Add {name} to lists'
|
defaultMessage='Add {name} to lists'
|
||||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||||
/>
|
/>
|
||||||
</span>
|
</NavigationFocusTarget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='dialog-modal__content'>
|
<div className='dialog-modal__content'>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type { List as ImmutableList, RecordOf } from 'immutable';
|
|||||||
|
|
||||||
import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
|
import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
|
||||||
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||||
|
import { FOCUS_TARGET } from '@/mastodon/components/navigation_focus_target';
|
||||||
import BarChart4BarsIcon from '@/material-icons/400-24px/bar_chart_4_bars.svg?react';
|
import BarChart4BarsIcon from '@/material-icons/400-24px/bar_chart_4_bars.svg?react';
|
||||||
import PhotoLibraryIcon from '@/material-icons/400-24px/photo_library.svg?react';
|
import PhotoLibraryIcon from '@/material-icons/400-24px/photo_library.svg?react';
|
||||||
import { toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
import { toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||||
@ -67,7 +68,7 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
|||||||
const path = `/@${account.acct}/${statusId}`;
|
const path = `/@${account.acct}/${statusId}`;
|
||||||
|
|
||||||
if (button === 0 && !(ctrlKey || metaKey)) {
|
if (button === 0 && !(ctrlKey || metaKey)) {
|
||||||
history.push(path);
|
history.push(path, { focusTarget: FOCUS_TARGET.POST });
|
||||||
} else if (button === 1 || (button === 0 && (ctrlKey || metaKey))) {
|
} else if (button === 1 || (button === 0 && (ctrlKey || metaKey))) {
|
||||||
window.open(path, '_blank', 'noopener');
|
window.open(path, '_blank', 'noopener');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,15 @@ import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
|||||||
|
|
||||||
import { Helmet } from '@unhead/react/helmet';
|
import { Helmet } from '@unhead/react/helmet';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { apiGetPrivacyPolicy } from 'mastodon/api/instance';
|
import { apiGetPrivacyPolicy } from 'mastodon/api/instance';
|
||||||
import type { ApiPrivacyPolicyJSON } from 'mastodon/api_types/instance';
|
import type { ApiPrivacyPolicyJSON } from 'mastodon/api_types/instance';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
|
import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
|
||||||
import { Skeleton } from 'mastodon/components/skeleton';
|
import { Skeleton } from 'mastodon/components/skeleton';
|
||||||
|
|
||||||
|
import { getColumnSkipLinkId } from '../ui/components/skip_links';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
|
title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
|
||||||
});
|
});
|
||||||
@ -40,12 +43,12 @@ const PrivacyPolicy: React.FC<{
|
|||||||
>
|
>
|
||||||
<div className='scrollable privacy-policy'>
|
<div className='scrollable privacy-policy'>
|
||||||
<div className='column-title'>
|
<div className='column-title'>
|
||||||
<h3>
|
<NavigationFocusTarget as='h1' id={getColumnSkipLinkId(1)}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='privacy_policy.title'
|
id='privacy_policy.title'
|
||||||
defaultMessage='Privacy Policy'
|
defaultMessage='Privacy Policy'
|
||||||
/>
|
/>
|
||||||
</h3>
|
</NavigationFocusTarget>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='privacy_policy.last_updated'
|
id='privacy_policy.last_updated'
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import { PureComponent } from 'react';
|
|||||||
|
|
||||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { injectIntl } from '@/mastodon/components/intl';
|
import { injectIntl } from '@/mastodon/components/intl';
|
||||||
|
|
||||||
import Option from './components/option';
|
import Option from './components/option';
|
||||||
@ -84,7 +83,9 @@ class Category extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
<FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
|
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { OrderedSet } from 'immutable';
|
|||||||
import { shallowEqual } from 'react-redux';
|
import { shallowEqual } from 'react-redux';
|
||||||
|
|
||||||
import { Toggle } from '@/mastodon/components/form_fields/toggle_field';
|
import { Toggle } from '@/mastodon/components/form_fields/toggle_field';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { fetchAccount } from 'mastodon/actions/accounts';
|
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import type { Status } from 'mastodon/models/status';
|
import type { Status } from 'mastodon/models/status';
|
||||||
@ -148,14 +149,18 @@ const Comment: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title' id={titleId}>
|
<NavigationFocusTarget
|
||||||
|
as='h1'
|
||||||
|
id={titleId}
|
||||||
|
className='report-dialog-modal__title'
|
||||||
|
>
|
||||||
{modalTitle ?? (
|
{modalTitle ?? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='report.comment.title'
|
id='report.comment.title'
|
||||||
defaultMessage='Is there anything else you think we should know?'
|
defaultMessage='Is there anything else you think we should know?'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</NavigationFocusTarget>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
className='report-dialog-modal__textarea'
|
className='report-dialog-modal__textarea'
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
import Option from './components/option';
|
import Option from './components/option';
|
||||||
|
|
||||||
@ -40,7 +41,9 @@ class Rules extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
<FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
|
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { connect } from 'react-redux';
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
|
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
|
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
|
||||||
@ -37,7 +38,9 @@ class Statuses extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
<FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
|
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
|
||||||
|
|
||||||
<div className='report-dialog-modal__statuses'>
|
<div className='report-dialog-modal__statuses'>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
blockAccount,
|
blockAccount,
|
||||||
} from 'mastodon/actions/accounts';
|
} from 'mastodon/actions/accounts';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
const mapStateToProps = () => ({});
|
const mapStateToProps = () => ({});
|
||||||
|
|
||||||
@ -52,7 +53,9 @@ class Thanks extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
|
{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
|
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p>
|
||||||
|
|
||||||
{account.getIn(['relationship', 'following']) && (
|
{account.getIn(['relationship', 'following']) && (
|
||||||
|
|||||||
@ -72,6 +72,7 @@ import ActionBar from './components/action_bar';
|
|||||||
import { DetailedStatus } from './components/detailed_status';
|
import { DetailedStatus } from './components/detailed_status';
|
||||||
import { RefreshController } from './components/refresh_controller';
|
import { RefreshController } from './components/refresh_controller';
|
||||||
import { quoteComposeById } from '@/mastodon/actions/compose_typed';
|
import { quoteComposeById } from '@/mastodon/actions/compose_typed';
|
||||||
|
import { FOCUS_TARGET, NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
||||||
@ -585,7 +586,13 @@ class Status extends ImmutablePureComponent {
|
|||||||
{ancestors}
|
{ancestors}
|
||||||
|
|
||||||
<Hotkeys handlers={handlers}>
|
<Hotkeys handlers={handlers}>
|
||||||
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader({intl, status})} ref={this.setStatusRef}>
|
<NavigationFocusTarget
|
||||||
|
as='div'
|
||||||
|
focusTargetName={FOCUS_TARGET.POST}
|
||||||
|
className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)}
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={textForScreenReader({intl, status})} ref={this.setStatusRef}
|
||||||
|
>
|
||||||
<DetailedStatus
|
<DetailedStatus
|
||||||
key={`details-${status.get('id')}`}
|
key={`details-${status.get('id')}`}
|
||||||
status={status}
|
status={status}
|
||||||
@ -626,7 +633,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
onPin={this.handlePin}
|
onPin={this.handlePin}
|
||||||
onEmbed={this.handleEmbed}
|
onEmbed={this.handleEmbed}
|
||||||
/>
|
/>
|
||||||
</div>
|
</NavigationFocusTarget>
|
||||||
</Hotkeys>
|
</Hotkeys>
|
||||||
|
|
||||||
{descendants}
|
{descendants}
|
||||||
|
|||||||
@ -11,11 +11,14 @@ import { Link, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Helmet } from '@unhead/react/helmet';
|
import { Helmet } from '@unhead/react/helmet';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { apiGetTermsOfService } from 'mastodon/api/instance';
|
import { apiGetTermsOfService } from 'mastodon/api/instance';
|
||||||
import type { ApiTermsOfServiceJSON } from 'mastodon/api_types/instance';
|
import type { ApiTermsOfServiceJSON } from 'mastodon/api_types/instance';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { BundleColumnError } from 'mastodon/features/ui/components/bundle_column_error';
|
import { BundleColumnError } from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
|
|
||||||
|
import { getColumnSkipLinkId } from '../ui/components/skip_links';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'terms_of_service.title', defaultMessage: 'Terms of Service' },
|
title: { id: 'terms_of_service.title', defaultMessage: 'Terms of Service' },
|
||||||
});
|
});
|
||||||
@ -55,12 +58,12 @@ const TermsOfService: React.FC<{
|
|||||||
>
|
>
|
||||||
<div className='scrollable privacy-policy'>
|
<div className='scrollable privacy-policy'>
|
||||||
<div className='column-title'>
|
<div className='column-title'>
|
||||||
<h3>
|
<NavigationFocusTarget as='h1' id={getColumnSkipLinkId(1)}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='terms_of_service.title'
|
id='terms_of_service.title'
|
||||||
defaultMessage='Terms of Service'
|
defaultMessage='Terms of Service'
|
||||||
/>
|
/>
|
||||||
</h3>
|
</NavigationFocusTarget>
|
||||||
<p className='prose'>
|
<p className='prose'>
|
||||||
{response?.effective ? (
|
{response?.effective ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { render, fireEvent, screen } from '@/testing/rendering';
|
import { render, fireEvent, screen } from '@/testing/rendering';
|
||||||
|
|
||||||
import Column from '../column';
|
import Column from '../column';
|
||||||
|
import { FocusTargetProvider } from '@/mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
const fakeIcon = () => <span />;
|
const fakeIcon = () => <span />;
|
||||||
|
|
||||||
@ -9,9 +10,11 @@ describe('<Column />', () => {
|
|||||||
it('runs the scroll animation if the column contains scrollable content', () => {
|
it('runs the scroll animation if the column contains scrollable content', () => {
|
||||||
const scrollToMock = vi.fn();
|
const scrollToMock = vi.fn();
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<Column heading='notifications' icon='notifications' iconComponent={fakeIcon}>
|
<FocusTargetProvider>
|
||||||
<div className='scrollable' />
|
<Column heading='notifications' icon='notifications' iconComponent={fakeIcon}>
|
||||||
</Column>,
|
<div className='scrollable' />
|
||||||
|
</Column>
|
||||||
|
</FocusTargetProvider>,
|
||||||
);
|
);
|
||||||
container.querySelector('.scrollable').scrollTo = scrollToMock;
|
container.querySelector('.scrollable').scrollTo = scrollToMock;
|
||||||
fireEvent.click(screen.getByText('notifications'));
|
fireEvent.click(screen.getByText('notifications'));
|
||||||
@ -19,7 +22,11 @@ describe('<Column />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not try to scroll if there is no scrollable content', () => {
|
it('does not try to scroll if there is no scrollable content', () => {
|
||||||
render(<Column heading='notifications' icon='notifications' iconComponent={fakeIcon} />);
|
render(
|
||||||
|
<FocusTargetProvider>
|
||||||
|
<Column heading='notifications' icon='notifications' iconComponent={fakeIcon} />
|
||||||
|
</FocusTargetProvider>
|
||||||
|
);
|
||||||
fireEvent.click(screen.getByText('notifications'));
|
fireEvent.click(screen.getByText('notifications'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
|||||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||||
import { blockAccount } from 'mastodon/actions/accounts';
|
import { blockAccount } from 'mastodon/actions/accounts';
|
||||||
import { closeModal } from 'mastodon/actions/modal';
|
import { closeModal } from 'mastodon/actions/modal';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
|
||||||
@ -46,7 +47,9 @@ export const BlockModal = ({ accountId, acct }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1><FormattedMessage id='block_modal.title' defaultMessage='Block user?' /></h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
<FormattedMessage id='block_modal.title' defaultMessage='Block user?' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p>@{acct}</p>
|
<p>@{acct}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import classNames from 'classnames';
|
|||||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||||
import { EmbeddedStatus } from 'mastodon/features/notifications_v2/components/embedded_status';
|
import { EmbeddedStatus } from 'mastodon/features/notifications_v2/components/embedded_status';
|
||||||
import type { Status, StatusVisibility } from 'mastodon/models/status';
|
import type { Status, StatusVisibility } from 'mastodon/models/status';
|
||||||
@ -68,7 +69,7 @@ export const BoostModal: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
{status.get('reblogged') ? (
|
{status.get('reblogged') ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='boost_modal.undo_reblog'
|
id='boost_modal.undo_reblog'
|
||||||
@ -80,7 +81,7 @@ export const BoostModal: React.FC<{
|
|||||||
defaultMessage='Boost post?'
|
defaultMessage='Boost post?'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</NavigationFocusTarget>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='boost_modal.combo'
|
id='boost_modal.combo'
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import {
|
import {
|
||||||
ModalShell,
|
ModalShell,
|
||||||
@ -67,7 +68,13 @@ export const ConfirmationModal: React.FC<
|
|||||||
return (
|
return (
|
||||||
<ModalShell onSubmit={handleClick}>
|
<ModalShell onSubmit={handleClick}>
|
||||||
<ModalShellBody className={className}>
|
<ModalShellBody className={className}>
|
||||||
<h1 id={titleId}>{title}</h1>
|
{noFocusButton ? (
|
||||||
|
<NavigationFocusTarget as='h1' id={titleId}>
|
||||||
|
{title}
|
||||||
|
</NavigationFocusTarget>
|
||||||
|
) : (
|
||||||
|
<h1>{title}</h1>
|
||||||
|
)}
|
||||||
{message && <p>{message}</p>}
|
{message && <p>{message}</p>}
|
||||||
|
|
||||||
{extraContent ?? children}
|
{extraContent ?? children}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import { Button } from '@/mastodon/components/button';
|
import { Button } from '@/mastodon/components/button';
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
|
||||||
export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal';
|
export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal';
|
||||||
@ -47,7 +48,9 @@ export const DialogModal: FC<DialogModalProps> = ({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h1 className='dialog-modal__header__title'>{title}</h1>
|
<NavigationFocusTarget as='h1' className='dialog-modal__header__title'>
|
||||||
|
{title}
|
||||||
|
</NavigationFocusTarget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='dialog-modal__content'>
|
<div className='dialog-modal__content'>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { apiRequest } from 'mastodon/api';
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
@ -77,12 +78,12 @@ export const DomainBlockModal: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='domain_block_modal.title'
|
id='domain_block_modal.title'
|
||||||
defaultMessage='Block domain?'
|
defaultMessage='Block domain?'
|
||||||
/>
|
/>
|
||||||
</h1>
|
</NavigationFocusTarget>
|
||||||
<p>{domain}</p>
|
<p>{domain}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { showAlertForError } from 'mastodon/actions/alerts';
|
|||||||
import api from 'mastodon/api';
|
import api from 'mastodon/api';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
|
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
interface OEmbedResponse {
|
interface OEmbedResponse {
|
||||||
@ -76,9 +77,9 @@ const EmbedModal: React.FC<{
|
|||||||
<Button onClick={onClose}>
|
<Button onClick={onClose}>
|
||||||
<FormattedMessage id='report.close' defaultMessage='Done' />
|
<FormattedMessage id='report.close' defaultMessage='Done' />
|
||||||
</Button>
|
</Button>
|
||||||
<span className='dialog-modal__header__title'>
|
<NavigationFocusTarget as='h1' className='dialog-modal__header__title'>
|
||||||
<FormattedMessage id='status.embed' defaultMessage='Get embed code' />
|
<FormattedMessage id='status.embed' defaultMessage='Get embed code' />
|
||||||
</span>
|
</NavigationFocusTarget>
|
||||||
<Button secondary onClick={onClose}>
|
<Button secondary onClick={onClose}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='confirmation_modal.cancel'
|
id='confirmation_modal.cancel'
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { closeModal } from 'mastodon/actions/modal';
|
|||||||
import { updateNotificationsPolicy } from 'mastodon/actions/notification_policies';
|
import { updateNotificationsPolicy } from 'mastodon/actions/notification_policies';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
|
|
||||||
export const IgnoreNotificationsModal = ({ filterType }) => {
|
export const IgnoreNotificationsModal = ({ filterType }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -57,7 +58,7 @@ export const IgnoreNotificationsModal = ({ filterType }) => {
|
|||||||
<div className='modal-root__modal safety-action-modal'>
|
<div className='modal-root__modal safety-action-modal'>
|
||||||
<div className='safety-action-modal__top'>
|
<div className='safety-action-modal__top'>
|
||||||
<div className='safety-action-modal__header'>
|
<div className='safety-action-modal__header'>
|
||||||
<h1>{title}</h1>
|
<NavigationFocusTarget as='h1'>{title}</NavigationFocusTarget>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className='safety-action-modal__bullet-points'>
|
<ul className='safety-action-modal__bullet-points'>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { closeModal } from 'mastodon/actions/modal';
|
|||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { CheckBox } from 'mastodon/components/check_box';
|
import { CheckBox } from 'mastodon/components/check_box';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { RadioButton } from 'mastodon/components/radio_button';
|
import { RadioButton } from 'mastodon/components/radio_button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -84,7 +85,9 @@ export const MuteModal = ({ accountId, acct }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1><FormattedMessage id='mute_modal.title' defaultMessage='Mute user?' /></h1>
|
<NavigationFocusTarget as='h1'>
|
||||||
|
<FormattedMessage id='mute_modal.title' defaultMessage='Mute user?' />
|
||||||
|
</NavigationFocusTarget>
|
||||||
<p>@{acct}</p>
|
<p>@{acct}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { fetchServer } from 'mastodon/actions/server';
|
|||||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
import { NavigationFocusTarget } from 'mastodon/components/navigation_focus_target';
|
||||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ const CollectionThanks: React.FC<{
|
|||||||
}> = ({ onClose }) => {
|
}> = ({ onClose }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3 className='report-dialog-modal__title'>
|
<NavigationFocusTarget as='h1' className='report-dialog-modal__title'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='report.thanks.title_actionable'
|
id='report.thanks.title_actionable'
|
||||||
defaultMessage="Thanks for reporting, we'll look into this."
|
defaultMessage="Thanks for reporting, we'll look into this."
|
||||||
/>
|
/>
|
||||||
</h3>
|
</NavigationFocusTarget>
|
||||||
|
|
||||||
<div className='flex-spacer' />
|
<div className='flex-spacer' />
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { Button } from '@/mastodon/components/button';
|
|||||||
import { Dropdown } from '@/mastodon/components/dropdown';
|
import { Dropdown } from '@/mastodon/components/dropdown';
|
||||||
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
import type { SelectItem } from '@/mastodon/components/dropdown_selector';
|
||||||
import { IconButton } from '@/mastodon/components/icon_button';
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
|
import { NavigationFocusTarget } from '@/mastodon/components/navigation_focus_target';
|
||||||
import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown';
|
import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown';
|
||||||
import { createAppSelector, useAppSelector } from '@/mastodon/store';
|
import { createAppSelector, useAppSelector } from '@/mastodon/store';
|
||||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||||
@ -217,14 +218,15 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
|||||||
iconComponent={CloseIcon}
|
iconComponent={CloseIcon}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
<FormattedMessage
|
<NavigationFocusTarget
|
||||||
id='visibility_modal.header'
|
as='h1'
|
||||||
defaultMessage='Visibility and interaction'
|
className='dialog-modal__header__title'
|
||||||
>
|
>
|
||||||
{(chunks) => (
|
<FormattedMessage
|
||||||
<span className='dialog-modal__header__title'>{chunks}</span>
|
id='visibility_modal.header'
|
||||||
)}
|
defaultMessage='Visibility and interaction'
|
||||||
</FormattedMessage>
|
/>
|
||||||
|
</NavigationFocusTarget>
|
||||||
</div>
|
</div>
|
||||||
<div className='dialog-modal__content'>
|
<div className='dialog-modal__content'>
|
||||||
<div className='dialog-modal__content__description'>
|
<div className='dialog-modal__content__description'>
|
||||||
|
|||||||
@ -187,7 +187,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||||||
<ColumnsContextProvider multiColumn={!singleColumn}>
|
<ColumnsContextProvider multiColumn={!singleColumn}>
|
||||||
<ColumnsArea ref={this.setRef} singleColumn={singleColumn} domain={domain} minimalShell={!signedIn && landingPage === 'overview'}>
|
<ColumnsArea ref={this.setRef} singleColumn={singleColumn} domain={domain} minimalShell={!signedIn && landingPage === 'overview'}>
|
||||||
<WrappedSwitch>
|
<WrappedSwitch>
|
||||||
<Redirect from='/' to={{pathname: rootRedirect, state: this.props.location.state}} exact />
|
<Redirect from='/' to={{pathname: rootRedirect, state: {...this.props.location.state, focusTarget: false}}} exact />
|
||||||
|
|
||||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={{...this.props.location, pathname: pathName.slice(5)}} /> : null}
|
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={{...this.props.location, pathname: pathName.slice(5)}} /> : null}
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Прагледзець абнаўленні",
|
"home.pending_critical_update.link": "Прагледзець абнаўленні",
|
||||||
"home.pending_critical_update.title": "Даступна крытычнае абнаўленне бяспекі!",
|
"home.pending_critical_update.title": "Даступна крытычнае абнаўленне бяспекі!",
|
||||||
"home.show_announcements": "Паказаць аб'явы",
|
"home.show_announcements": "Паказаць аб'явы",
|
||||||
|
"ignore_notifications_modal.bots_title": "Ігнараваць апавяшчэнні ад ботаў?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што Вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.",
|
"ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што Вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.",
|
||||||
"ignore_notifications_modal.filter_instead": "Замест гэтага адфільтраваць",
|
"ignore_notifications_modal.filter_instead": "Замест гэтага адфільтраваць",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Вы па-ранейшаму зможаце прымаць, адхіляць ці скардзіцца на карыстальнікаў",
|
"ignore_notifications_modal.filter_to_act_users": "Вы па-ранейшаму зможаце прымаць, адхіляць ці скардзіцца на карыстальнікаў",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Iгнараваць",
|
"notifications.policy.drop": "Iгнараваць",
|
||||||
"notifications.policy.drop_hint": "Адправіць у бездань, адкуль больш ніколі не ўбачыце",
|
"notifications.policy.drop_hint": "Адправіць у бездань, адкуль больш ніколі не ўбачыце",
|
||||||
"notifications.policy.filter": "Фільтраваць",
|
"notifications.policy.filter": "Фільтраваць",
|
||||||
|
"notifications.policy.filter_bots_hint": "Уліковыя запісы, пазначаныя як аўтаматычныя",
|
||||||
|
"notifications.policy.filter_bots_title": "Боты",
|
||||||
"notifications.policy.filter_hint": "Адправіць у скрыню адфільтраваных апавяшчэнняў",
|
"notifications.policy.filter_hint": "Адправіць у скрыню адфільтраваных апавяшчэнняў",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Абмежавана мадэратарамі сервера",
|
"notifications.policy.filter_limited_accounts_hint": "Абмежавана мадэратарамі сервера",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй",
|
"notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Se opdateringer",
|
"home.pending_critical_update.link": "Se opdateringer",
|
||||||
"home.pending_critical_update.title": "Kritisk sikkerhedsopdatering tilgængelig!",
|
"home.pending_critical_update.title": "Kritisk sikkerhedsopdatering tilgængelig!",
|
||||||
"home.show_announcements": "Vis bekendtgørelser",
|
"home.show_announcements": "Vis bekendtgørelser",
|
||||||
|
"ignore_notifications_modal.bots_title": "Ignorér notifikationer fra bots?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon kan ikke informere brugere om, at du har ignoreret deres notifikationer. At ignorere notifikationer forhindrer ikke selve beskederne i at blive sendt.",
|
"ignore_notifications_modal.disclaimer": "Mastodon kan ikke informere brugere om, at du har ignoreret deres notifikationer. At ignorere notifikationer forhindrer ikke selve beskederne i at blive sendt.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrér i stedet",
|
"ignore_notifications_modal.filter_instead": "Filtrér i stedet",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Du vil stadig kunne acceptere, afvise eller anmelde brugere",
|
"ignore_notifications_modal.filter_to_act_users": "Du vil stadig kunne acceptere, afvise eller anmelde brugere",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorér",
|
"notifications.policy.drop": "Ignorér",
|
||||||
"notifications.policy.drop_hint": "Send til intetheden, for aldrig at blive set igen",
|
"notifications.policy.drop_hint": "Send til intetheden, for aldrig at blive set igen",
|
||||||
"notifications.policy.filter": "Filter",
|
"notifications.policy.filter": "Filter",
|
||||||
|
"notifications.policy.filter_bots_hint": "Konti markeret som automatiseret",
|
||||||
|
"notifications.policy.filter_bots_title": "Bots",
|
||||||
"notifications.policy.filter_hint": "Send til filtrerede notifikationsindbakke",
|
"notifications.policy.filter_hint": "Send til filtrerede notifikationsindbakke",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
|
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
|
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Updates ansehen",
|
"home.pending_critical_update.link": "Updates ansehen",
|
||||||
"home.pending_critical_update.title": "Kritisches Sicherheitsupdate verfügbar!",
|
"home.pending_critical_update.title": "Kritisches Sicherheitsupdate verfügbar!",
|
||||||
"home.show_announcements": "Ankündigungen anzeigen",
|
"home.show_announcements": "Ankündigungen anzeigen",
|
||||||
|
"ignore_notifications_modal.bots_title": "Benachrichtigungen von Bots ignorieren?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon kann anderen Nutzer*innen nicht mitteilen, dass du deren Benachrichtigungen ignorierst. Das Ignorieren von Benachrichtigungen wird nicht das Absenden der Nachricht selbst unterbinden.",
|
"ignore_notifications_modal.disclaimer": "Mastodon kann anderen Nutzer*innen nicht mitteilen, dass du deren Benachrichtigungen ignorierst. Das Ignorieren von Benachrichtigungen wird nicht das Absenden der Nachricht selbst unterbinden.",
|
||||||
"ignore_notifications_modal.filter_instead": "Stattdessen filtern",
|
"ignore_notifications_modal.filter_instead": "Stattdessen filtern",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Du wirst weiterhin die Möglichkeit haben, andere Nutzer*innen zu akzeptieren, abzulehnen oder zu melden",
|
"ignore_notifications_modal.filter_to_act_users": "Du wirst weiterhin die Möglichkeit haben, andere Nutzer*innen zu akzeptieren, abzulehnen oder zu melden",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorieren",
|
"notifications.policy.drop": "Ignorieren",
|
||||||
"notifications.policy.drop_hint": "Ins Nirwana befördern und auf Nimmerwiedersehen!",
|
"notifications.policy.drop_hint": "Ins Nirwana befördern und auf Nimmerwiedersehen!",
|
||||||
"notifications.policy.filter": "Filtern",
|
"notifications.policy.filter": "Filtern",
|
||||||
|
"notifications.policy.filter_bots_hint": "Als automatisiert gekennzeichnete Konten",
|
||||||
|
"notifications.policy.filter_bots_title": "Bots",
|
||||||
"notifications.policy.filter_hint": "Im separaten Feed „Gefilterte Benachrichtigungen“ anzeigen",
|
"notifications.policy.filter_hint": "Im separaten Feed „Gefilterte Benachrichtigungen“ anzeigen",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Durch Server-Moderator*innen eingeschränkte Profile",
|
"notifications.policy.filter_limited_accounts_hint": "Durch Server-Moderator*innen eingeschränkte Profile",
|
||||||
"notifications.policy.filter_limited_accounts_title": "eingeschränkten Konten",
|
"notifications.policy.filter_limited_accounts_title": "eingeschränkten Konten",
|
||||||
|
|||||||
@ -577,7 +577,7 @@
|
|||||||
"content_warning.show_more": "Εμφάνιση περισσότερων",
|
"content_warning.show_more": "Εμφάνιση περισσότερων",
|
||||||
"content_warning.show_short": "Εμφάνιση",
|
"content_warning.show_short": "Εμφάνιση",
|
||||||
"conversation.delete": "Διαγραφή συνομιλίας",
|
"conversation.delete": "Διαγραφή συνομιλίας",
|
||||||
"conversation.mark_as_read": "Σήμανση ως αναγνωσμένη",
|
"conversation.mark_as_read": "Σήμανση ως διαβασμένη",
|
||||||
"conversation.open": "Προβολή συνομιλίας",
|
"conversation.open": "Προβολή συνομιλίας",
|
||||||
"conversation.with": "Με {names}",
|
"conversation.with": "Με {names}",
|
||||||
"copy_icon_button.copied": "Αντιγράφηκε στο πρόχειρο",
|
"copy_icon_button.copied": "Αντιγράφηκε στο πρόχειρο",
|
||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Δείτε ενημερώσεις",
|
"home.pending_critical_update.link": "Δείτε ενημερώσεις",
|
||||||
"home.pending_critical_update.title": "Κρίσιμη ενημέρωση ασφαλείας διαθέσιμη!",
|
"home.pending_critical_update.title": "Κρίσιμη ενημέρωση ασφαλείας διαθέσιμη!",
|
||||||
"home.show_announcements": "Εμφάνιση ανακοινώσεων",
|
"home.show_announcements": "Εμφάνιση ανακοινώσεων",
|
||||||
|
"ignore_notifications_modal.bots_title": "Αγνόηση ειδοποιήσεων από bots;",
|
||||||
"ignore_notifications_modal.disclaimer": "Το Mastodon δε μπορεί να ενημερώσει τους χρήστες ότι αγνόησες τις ειδοποιήσεις του. Η αγνόηση ειδοποιήσεων δεν θα εμποδίσει την αποστολή των ίδιων των μηνυμάτων.",
|
"ignore_notifications_modal.disclaimer": "Το Mastodon δε μπορεί να ενημερώσει τους χρήστες ότι αγνόησες τις ειδοποιήσεις του. Η αγνόηση ειδοποιήσεων δεν θα εμποδίσει την αποστολή των ίδιων των μηνυμάτων.",
|
||||||
"ignore_notifications_modal.filter_instead": "Φίλτρο αντ' αυτού",
|
"ignore_notifications_modal.filter_instead": "Φίλτρο αντ' αυτού",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Θα μπορείς ακόμη να αποδεχθείς, να απορρίψεις ή να αναφέρεις χρήστες",
|
"ignore_notifications_modal.filter_to_act_users": "Θα μπορείς ακόμη να αποδεχθείς, να απορρίψεις ή να αναφέρεις χρήστες",
|
||||||
@ -1011,41 +1012,43 @@
|
|||||||
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
||||||
"notifications.column_settings.follow": "Νέοι ακόλουθοι:",
|
"notifications.column_settings.follow": "Νέοι ακόλουθοι:",
|
||||||
"notifications.column_settings.follow_request": "Νέο αίτημα ακολούθησης:",
|
"notifications.column_settings.follow_request": "Νέο αίτημα ακολούθησης:",
|
||||||
"notifications.column_settings.group": "Ομάδα",
|
"notifications.column_settings.group": "Ομαδοποίηση",
|
||||||
"notifications.column_settings.mention": "Επισημάνσεις:",
|
"notifications.column_settings.mention": "Επισημάνσεις:",
|
||||||
"notifications.column_settings.poll": "Αποτελέσματα δημοσκόπησης:",
|
"notifications.column_settings.poll": "Αποτελέσματα δημοσκόπησης:",
|
||||||
"notifications.column_settings.push": "Ειδοποιήσεις Push",
|
"notifications.column_settings.push": "Ειδοποιήσεις Push",
|
||||||
"notifications.column_settings.quote": "Παραθέσεις:",
|
"notifications.column_settings.quote": "Παραθέσεις:",
|
||||||
"notifications.column_settings.reblog": "Ενισχύσεις:",
|
"notifications.column_settings.reblog": "Ενισχύσεις:",
|
||||||
"notifications.column_settings.show": "Εμφάνισε σε στήλη",
|
"notifications.column_settings.show": "Εμφάνιση σε στήλη",
|
||||||
"notifications.column_settings.sound": "Αναπαραγωγή ήχου",
|
"notifications.column_settings.sound": "Αναπαραγωγή ήχου",
|
||||||
"notifications.column_settings.status": "Νέες αναρτήσεις:",
|
"notifications.column_settings.status": "Νέες αναρτήσεις:",
|
||||||
"notifications.column_settings.unread_notifications.category": "Μη αναγνωσμένες ειδοποιήσεις",
|
"notifications.column_settings.unread_notifications.category": "Αδιάβαστες ειδοποιήσεις",
|
||||||
"notifications.column_settings.unread_notifications.highlight": "Επισήμανση μη αναγνωσμένων ειδοποιήσεων",
|
"notifications.column_settings.unread_notifications.highlight": "Επισήμανση αδιάβαστων ειδοποιήσεων",
|
||||||
"notifications.column_settings.update": "Επεξεργασίες:",
|
"notifications.column_settings.update": "Επεξεργασίες:",
|
||||||
"notifications.filter.all": "Όλες",
|
"notifications.filter.all": "Όλες",
|
||||||
"notifications.filter.boosts": "Προωθήσεις",
|
"notifications.filter.boosts": "Προωθήσεις",
|
||||||
"notifications.filter.collections": "Συλλογές",
|
"notifications.filter.collections": "Συλλογές",
|
||||||
"notifications.filter.favourites": "Αγαπημένα",
|
"notifications.filter.favourites": "Αγαπημένα",
|
||||||
"notifications.filter.follows": "Ακολουθείς",
|
"notifications.filter.follows": "Νέοι ακόλουθοι",
|
||||||
"notifications.filter.mentions": "Επισημάνσεις",
|
"notifications.filter.mentions": "Επισημάνσεις",
|
||||||
"notifications.filter.polls": "Αποτελέσματα δημοσκόπησης",
|
"notifications.filter.polls": "Αποτελέσματα δημοσκόπησης",
|
||||||
"notifications.filter.statuses": "Ενημερώσεις από όσους ακολουθείς",
|
"notifications.filter.statuses": "Ενημερώσεις από όσους ακολουθείς",
|
||||||
"notifications.grant_permission": "Χορήγηση άδειας.",
|
"notifications.grant_permission": "Χορήγηση άδειας.",
|
||||||
"notifications.group": "{count} ειδοποιήσεις",
|
"notifications.group": "{count} ειδοποιήσεις",
|
||||||
"notifications.mark_as_read": "Σήμανε όλες τις ειδοποιήσεις ως αναγνωσμένες",
|
"notifications.mark_as_read": "Σήμανε όλες τις ειδοποιήσεις ως διαβασμένες",
|
||||||
"notifications.permission_denied": "Οι ειδοποιήσεις για υπολογιστή δεν είναι διαθέσιμες διότι έχει απορριφθεί κάποιο προηγούμενο αίτημα άδειας",
|
"notifications.permission_denied": "Οι ειδοποιήσεις για υπολογιστή δεν είναι διαθέσιμες διότι έχει απορριφθεί κάποιο προηγούμενο αίτημα άδειας",
|
||||||
"notifications.permission_denied_alert": "Δεν είναι δυνατή η ενεργοποίηση των ειδοποιήσεων για υπολογιστή, καθώς η άδεια του προγράμματος περιήγησης έχει απορριφθεί προηγουμένων",
|
"notifications.permission_denied_alert": "Δεν είναι δυνατή η ενεργοποίηση των ειδοποιήσεων για υπολογιστή, καθώς η άδεια του προγράμματος περιήγησης έχει απορριφθεί προηγουμένως",
|
||||||
"notifications.permission_required": "Οι ειδοποιήσεις για υπολογιστή δεν είναι διαθέσιμες επειδή δεν έχει δοθεί η απαιτούμενη άδεια.",
|
"notifications.permission_required": "Οι ειδοποιήσεις για υπολογιστή δεν είναι διαθέσιμες επειδή δεν έχει δοθεί η απαιτούμενη άδεια.",
|
||||||
"notifications.policy.accept": "Αποδοχή",
|
"notifications.policy.accept": "Αποδοχή",
|
||||||
"notifications.policy.accept_hint": "Εμφάνιση στις ειδοποιήσεις",
|
"notifications.policy.accept_hint": "Εμφάνιση στις ειδοποιήσεις",
|
||||||
"notifications.policy.drop": "Αγνόηση",
|
"notifications.policy.drop": "Αγνόηση",
|
||||||
"notifications.policy.drop_hint": "Στείλε τες στο υπερπέραν, για να μην τις ξαναδείτε",
|
"notifications.policy.drop_hint": "Στείλε τες στο υπερπέραν, για να μην τις ξαναδείς",
|
||||||
"notifications.policy.filter": "Φίλτρο",
|
"notifications.policy.filter": "Φιλτράρισμα",
|
||||||
|
"notifications.policy.filter_bots_hint": "Λογαριασμοί σημασμένοι ως αυτοματοποιημένοι",
|
||||||
|
"notifications.policy.filter_bots_title": "Bots",
|
||||||
"notifications.policy.filter_hint": "Αποστολή στα εισερχόμενα φιλτραρισμένων ειδοποιήσεων",
|
"notifications.policy.filter_hint": "Αποστολή στα εισερχόμενα φιλτραρισμένων ειδοποιήσεων",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Περιορισμένη από συντονιστές διακομιστή",
|
"notifications.policy.filter_limited_accounts_hint": "Περιορισμένοι από συντονιστές διακομιστή",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Συντονισμένοι λογαριασμοί",
|
"notifications.policy.filter_limited_accounts_title": "Συντονισμένοι λογαριασμοί",
|
||||||
"notifications.policy.filter_new_accounts.hint": "Δημιουργήθηκε εντός {days, plural, one {της τελευταίας ημέρας} other {των τελευταίων # ημερών}}",
|
"notifications.policy.filter_new_accounts.hint": "Δημιουργημένοι εντός {days, plural, one {της τελευταίας ημέρας} other {των τελευταίων # ημερών}}",
|
||||||
"notifications.policy.filter_new_accounts_title": "Νέοι λογαριασμοί",
|
"notifications.policy.filter_new_accounts_title": "Νέοι λογαριασμοί",
|
||||||
"notifications.policy.filter_not_followers_hint": "Συμπεριλαμβανομένων των ατόμων που σας έχουν ακολουθήσει λιγότερο από {days, plural, one {μια ημέρα} other {# ημέρες}} πριν",
|
"notifications.policy.filter_not_followers_hint": "Συμπεριλαμβανομένων των ατόμων που σας έχουν ακολουθήσει λιγότερο από {days, plural, one {μια ημέρα} other {# ημέρες}} πριν",
|
||||||
"notifications.policy.filter_not_followers_title": "Άτομα που δε σε ακολουθούν",
|
"notifications.policy.filter_not_followers_title": "Άτομα που δε σε ακολουθούν",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Ver actualizaciones",
|
"home.pending_critical_update.link": "Ver actualizaciones",
|
||||||
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
||||||
"home.show_announcements": "Mostrar anuncios",
|
"home.show_announcements": "Mostrar anuncios",
|
||||||
|
"ignore_notifications_modal.bots_title": "¿Ignorar notificaciones de bots?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que ignoraste sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que ignoraste sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar",
|
"ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o denunciar a usuarios",
|
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o denunciar a usuarios",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorar",
|
"notifications.policy.drop": "Ignorar",
|
||||||
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
||||||
"notifications.policy.filter": "Filtrar",
|
"notifications.policy.filter": "Filtrar",
|
||||||
|
"notifications.policy.filter_bots_hint": "Cuentas marcadas como automatizadas",
|
||||||
|
"notifications.policy.filter_bots_title": "Botes",
|
||||||
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitada por los moderadores del servidor",
|
"notifications.policy.filter_limited_accounts_hint": "Limitada por los moderadores del servidor",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Ver actualizaciones",
|
"home.pending_critical_update.link": "Ver actualizaciones",
|
||||||
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
||||||
"home.show_announcements": "Mostrar anuncios",
|
"home.show_announcements": "Mostrar anuncios",
|
||||||
|
"ignore_notifications_modal.bots_title": "¿Ignorar notificaciones de bots?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrar en su lugar",
|
"ignore_notifications_modal.filter_instead": "Filtrar en su lugar",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios",
|
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorar",
|
"notifications.policy.drop": "Ignorar",
|
||||||
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
||||||
"notifications.policy.filter": "Filtrar",
|
"notifications.policy.filter": "Filtrar",
|
||||||
|
"notifications.policy.filter_bots_hint": "Cuentas etiquetadas como automatizadas",
|
||||||
|
"notifications.policy.filter_bots_title": "Bots",
|
||||||
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitadas por los moderadores del servidor",
|
"notifications.policy.filter_limited_accounts_hint": "Limitadas por los moderadores del servidor",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Ver actualizaciones",
|
"home.pending_critical_update.link": "Ver actualizaciones",
|
||||||
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
"home.pending_critical_update.title": "¡Actualización de seguridad crítica disponible!",
|
||||||
"home.show_announcements": "Mostrar comunicaciones",
|
"home.show_announcements": "Mostrar comunicaciones",
|
||||||
|
"ignore_notifications_modal.bots_title": "¿Ignorar notificaciones de bots?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios de que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
"ignore_notifications_modal.disclaimer": "Mastodon no puede informar a los usuarios de que has ignorado sus notificaciones. Ignorar notificaciones no impedirá que se sigan enviando los mensajes.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar",
|
"ignore_notifications_modal.filter_instead": "Filtrar en vez de ignorar",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios",
|
"ignore_notifications_modal.filter_to_act_users": "Aún podrás aceptar, rechazar o reportar usuarios",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorar",
|
"notifications.policy.drop": "Ignorar",
|
||||||
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
"notifications.policy.drop_hint": "Enviar al vacío, no volver a mostrar nunca",
|
||||||
"notifications.policy.filter": "Filtrar",
|
"notifications.policy.filter": "Filtrar",
|
||||||
|
"notifications.policy.filter_bots_hint": "Cuentas etiquetadas como automatizadas",
|
||||||
|
"notifications.policy.filter_bots_title": "Bots",
|
||||||
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
"notifications.policy.filter_hint": "Enviar a la bandeja de entrada de notificaciones filtradas",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitadas por los moderadores del servidor",
|
"notifications.policy.filter_limited_accounts_hint": "Limitadas por los moderadores del servidor",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Tutustu päivityssisältöihin",
|
"home.pending_critical_update.link": "Tutustu päivityssisältöihin",
|
||||||
"home.pending_critical_update.title": "Kriittinen tietoturvapäivitys saatavilla!",
|
"home.pending_critical_update.title": "Kriittinen tietoturvapäivitys saatavilla!",
|
||||||
"home.show_announcements": "Näytä tiedotteet",
|
"home.show_announcements": "Näytä tiedotteet",
|
||||||
|
"ignore_notifications_modal.bots_title": "Sivuutetaanko ilmoitukset boteilta?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon ei voi ilmoittaa käyttäjille, että olet sivuuttanut heidän ilmoituksensa. Ilmoitusten sivuuttaminen ei lopeta itse viestien lähetystä.",
|
"ignore_notifications_modal.disclaimer": "Mastodon ei voi ilmoittaa käyttäjille, että olet sivuuttanut heidän ilmoituksensa. Ilmoitusten sivuuttaminen ei lopeta itse viestien lähetystä.",
|
||||||
"ignore_notifications_modal.filter_instead": "Suodata sen sijaan",
|
"ignore_notifications_modal.filter_instead": "Suodata sen sijaan",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Voit kuitenkin yhä hyväksyä, hylätä tai raportoida käyttäjiä",
|
"ignore_notifications_modal.filter_to_act_users": "Voit kuitenkin yhä hyväksyä, hylätä tai raportoida käyttäjiä",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Sivuuta",
|
"notifications.policy.drop": "Sivuuta",
|
||||||
"notifications.policy.drop_hint": "Lähetä tyhjyyteen, jotta et näe niitä enää koskaan",
|
"notifications.policy.drop_hint": "Lähetä tyhjyyteen, jotta et näe niitä enää koskaan",
|
||||||
"notifications.policy.filter": "Suodata",
|
"notifications.policy.filter": "Suodata",
|
||||||
|
"notifications.policy.filter_bots_hint": "Automatisoiduiksi merkityt tilit",
|
||||||
|
"notifications.policy.filter_bots_title": "Botit",
|
||||||
"notifications.policy.filter_hint": "Lähetä suodatettuihin ilmoituksiin",
|
"notifications.policy.filter_hint": "Lähetä suodatettuihin ilmoituksiin",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Palvelimen moderaattorien rajoittamat",
|
"notifications.policy.filter_limited_accounts_hint": "Palvelimen moderaattorien rajoittamat",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Moderoidut tilit",
|
"notifications.policy.filter_limited_accounts_title": "Moderoidut tilit",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Féach nuashonruithe",
|
"home.pending_critical_update.link": "Féach nuashonruithe",
|
||||||
"home.pending_critical_update.title": "Nuashonrú slándála ríthábhachtach ar fáil!",
|
"home.pending_critical_update.title": "Nuashonrú slándála ríthábhachtach ar fáil!",
|
||||||
"home.show_announcements": "Taispeáin fógraí",
|
"home.show_announcements": "Taispeáin fógraí",
|
||||||
|
"ignore_notifications_modal.bots_title": "Neamhaird a dhéanamh d’fhógraí ó bhotaí?",
|
||||||
"ignore_notifications_modal.disclaimer": "Ní féidir le Mastodon úsáideoirí a chur ar an eolas gur thug tú neamhaird dá bhfógraí. Má dhéantar neamhaird de fhógraí, ní stopfar na teachtaireachtaí iad féin a sheoladh.",
|
"ignore_notifications_modal.disclaimer": "Ní féidir le Mastodon úsáideoirí a chur ar an eolas gur thug tú neamhaird dá bhfógraí. Má dhéantar neamhaird de fhógraí, ní stopfar na teachtaireachtaí iad féin a sheoladh.",
|
||||||
"ignore_notifications_modal.filter_instead": "Scag ina ionad sin",
|
"ignore_notifications_modal.filter_instead": "Scag ina ionad sin",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Beidh tú fós in ann glacadh le húsáideoirí, iad a dhiúltú nó a thuairisciú",
|
"ignore_notifications_modal.filter_to_act_users": "Beidh tú fós in ann glacadh le húsáideoirí, iad a dhiúltú nó a thuairisciú",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Déan neamhaird de",
|
"notifications.policy.drop": "Déan neamhaird de",
|
||||||
"notifications.policy.drop_hint": "Seol chuig an neamhní, gan a bheith le feiceáil arís",
|
"notifications.policy.drop_hint": "Seol chuig an neamhní, gan a bheith le feiceáil arís",
|
||||||
"notifications.policy.filter": "Scagaire",
|
"notifications.policy.filter": "Scagaire",
|
||||||
|
"notifications.policy.filter_bots_hint": "Cuntais atá marcáilte mar uathoibrithe",
|
||||||
|
"notifications.policy.filter_bots_title": "Botanna",
|
||||||
"notifications.policy.filter_hint": "Seol chuig an mbosca isteach fógraí scagtha",
|
"notifications.policy.filter_hint": "Seol chuig an mbosca isteach fógraí scagtha",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Teoranta ag modhnóirí freastalaí",
|
"notifications.policy.filter_limited_accounts_hint": "Teoranta ag modhnóirí freastalaí",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Cuntais mhodhnaithe",
|
"notifications.policy.filter_limited_accounts_title": "Cuntais mhodhnaithe",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Mira as actualizacións",
|
"home.pending_critical_update.link": "Mira as actualizacións",
|
||||||
"home.pending_critical_update.title": "Hai una actualización crítica de seguridade!",
|
"home.pending_critical_update.title": "Hai una actualización crítica de seguridade!",
|
||||||
"home.show_announcements": "Amosar anuncios",
|
"home.show_announcements": "Amosar anuncios",
|
||||||
|
"ignore_notifications_modal.bots_title": "Ignorar as notificacións dos robots?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon non pode informar ás usuarias de que ignoraches as súas notificacións. Ao ignorar as notificacións non evitarás que as mensaxes sexan enviadas igualmente.",
|
"ignore_notifications_modal.disclaimer": "Mastodon non pode informar ás usuarias de que ignoraches as súas notificacións. Ao ignorar as notificacións non evitarás que as mensaxes sexan enviadas igualmente.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrar igualmente",
|
"ignore_notifications_modal.filter_instead": "Filtrar igualmente",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Poderás seguir aceptando, rexeitando e denunciando usuarias",
|
"ignore_notifications_modal.filter_to_act_users": "Poderás seguir aceptando, rexeitando e denunciando usuarias",
|
||||||
@ -846,6 +847,7 @@
|
|||||||
"keyboard_shortcuts.toggle_sensitivity": "Para amosar/agochar contido multimedia",
|
"keyboard_shortcuts.toggle_sensitivity": "Para amosar/agochar contido multimedia",
|
||||||
"keyboard_shortcuts.toot": "Para escribir unha nova publicación",
|
"keyboard_shortcuts.toot": "Para escribir unha nova publicación",
|
||||||
"keyboard_shortcuts.top": "Mover arriba de todo",
|
"keyboard_shortcuts.top": "Mover arriba de todo",
|
||||||
|
"keyboard_shortcuts.translate": "Traducir unha publicación",
|
||||||
"keyboard_shortcuts.unfocus": "Para deixar de destacar a área de escritura/procura",
|
"keyboard_shortcuts.unfocus": "Para deixar de destacar a área de escritura/procura",
|
||||||
"keyboard_shortcuts.up": "Para mover cara arriba na listaxe",
|
"keyboard_shortcuts.up": "Para mover cara arriba na listaxe",
|
||||||
"learn_more_link.got_it": "Entendo",
|
"learn_more_link.got_it": "Entendo",
|
||||||
@ -1041,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorar",
|
"notifications.policy.drop": "Ignorar",
|
||||||
"notifications.policy.drop_hint": "Esquecer isto, non volver a velo",
|
"notifications.policy.drop_hint": "Esquecer isto, non volver a velo",
|
||||||
"notifications.policy.filter": "Filtrar",
|
"notifications.policy.filter": "Filtrar",
|
||||||
|
"notifications.policy.filter_bots_hint": "Contas marcadas como automatizadas",
|
||||||
|
"notifications.policy.filter_bots_title": "Robots",
|
||||||
"notifications.policy.filter_hint": "Enviar á caixa de notificacións filtradas",
|
"notifications.policy.filter_hint": "Enviar á caixa de notificacións filtradas",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitada pola moderación do servidor",
|
"notifications.policy.filter_limited_accounts_hint": "Limitada pola moderación do servidor",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Contas moderadas",
|
"notifications.policy.filter_limited_accounts_title": "Contas moderadas",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "צפיה בעדכונים",
|
"home.pending_critical_update.link": "צפיה בעדכונים",
|
||||||
"home.pending_critical_update.title": "יצא עדכון אבטחה חשוב!",
|
"home.pending_critical_update.title": "יצא עדכון אבטחה חשוב!",
|
||||||
"home.show_announcements": "הצג הכרזות",
|
"home.show_announcements": "הצג הכרזות",
|
||||||
|
"ignore_notifications_modal.bots_title": "להתעלם מהתראות מחשבונות רובוטיים?",
|
||||||
"ignore_notifications_modal.disclaimer": "מסטודון אינו יכול ליידע משתמשים שהתעלמתם מהתראותיהם. התעלמות מהתראות לא תחסום את ההודעות עצמן מלהשלח.",
|
"ignore_notifications_modal.disclaimer": "מסטודון אינו יכול ליידע משתמשים שהתעלמתם מהתראותיהם. התעלמות מהתראות לא תחסום את ההודעות עצמן מלהשלח.",
|
||||||
"ignore_notifications_modal.filter_instead": "לסנן במקום",
|
"ignore_notifications_modal.filter_instead": "לסנן במקום",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "עדיין ביכולתך לקבל, לדחות ולדווח על משתמשים אחרים",
|
"ignore_notifications_modal.filter_to_act_users": "עדיין ביכולתך לקבל, לדחות ולדווח על משתמשים אחרים",
|
||||||
@ -846,6 +847,7 @@
|
|||||||
"keyboard_shortcuts.toggle_sensitivity": "הצגת/הסתרת מדיה",
|
"keyboard_shortcuts.toggle_sensitivity": "הצגת/הסתרת מדיה",
|
||||||
"keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש",
|
"keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש",
|
||||||
"keyboard_shortcuts.top": "העברה לראש הרשימה",
|
"keyboard_shortcuts.top": "העברה לראש הרשימה",
|
||||||
|
"keyboard_shortcuts.translate": "לתרגם הודעה",
|
||||||
"keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש",
|
"keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש",
|
||||||
"keyboard_shortcuts.up": "לנוע במעלה הרשימה",
|
"keyboard_shortcuts.up": "לנוע במעלה הרשימה",
|
||||||
"learn_more_link.got_it": "הבנתי",
|
"learn_more_link.got_it": "הבנתי",
|
||||||
@ -1041,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "להתעלם",
|
"notifications.policy.drop": "להתעלם",
|
||||||
"notifications.policy.drop_hint": "שליחה אל מצולות הנשיה, ולא יוודעו אודותיה לעולם",
|
"notifications.policy.drop_hint": "שליחה אל מצולות הנשיה, ולא יוודעו אודותיה לעולם",
|
||||||
"notifications.policy.filter": "מסנן",
|
"notifications.policy.filter": "מסנן",
|
||||||
|
"notifications.policy.filter_bots_hint": "חשבונות המסומנים כאוטומטיים",
|
||||||
|
"notifications.policy.filter_bots_title": "בוטים",
|
||||||
"notifications.policy.filter_hint": "שליחה לתיבה נכנסת מסוננת",
|
"notifications.policy.filter_hint": "שליחה לתיבה נכנסת מסוננת",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "הוגבל על ידי מנהלי הדיונים",
|
"notifications.policy.filter_limited_accounts_hint": "הוגבל על ידי מנהלי הדיונים",
|
||||||
"notifications.policy.filter_limited_accounts_title": "חשבומות תחת ניהול תוכן",
|
"notifications.policy.filter_limited_accounts_title": "חשבומות תחת ניהול תוכן",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Skoða uppfærslur",
|
"home.pending_critical_update.link": "Skoða uppfærslur",
|
||||||
"home.pending_critical_update.title": "Áríðandi öryggisuppfærsla er tiltæk!",
|
"home.pending_critical_update.title": "Áríðandi öryggisuppfærsla er tiltæk!",
|
||||||
"home.show_announcements": "Birta auglýsingar",
|
"home.show_announcements": "Birta auglýsingar",
|
||||||
|
"ignore_notifications_modal.bots_title": "Hunsa tilkynningar frá yrkjum?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon getur ekki upplýst notendur um að þú hunsir tilkynningar frá þeim. Hunsun tilkynninga kemur ekki í veg fyrir að sjálf skilaboðin verði send.",
|
"ignore_notifications_modal.disclaimer": "Mastodon getur ekki upplýst notendur um að þú hunsir tilkynningar frá þeim. Hunsun tilkynninga kemur ekki í veg fyrir að sjálf skilaboðin verði send.",
|
||||||
"ignore_notifications_modal.filter_instead": "Sía frekar",
|
"ignore_notifications_modal.filter_instead": "Sía frekar",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Þú munt áfram geta samþykkt, hafnað eða kært notendur",
|
"ignore_notifications_modal.filter_to_act_users": "Þú munt áfram geta samþykkt, hafnað eða kært notendur",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Hunsa",
|
"notifications.policy.drop": "Hunsa",
|
||||||
"notifications.policy.drop_hint": "Senda út í tómið, svo það sjáist aldrei framar",
|
"notifications.policy.drop_hint": "Senda út í tómið, svo það sjáist aldrei framar",
|
||||||
"notifications.policy.filter": "Sía",
|
"notifications.policy.filter": "Sía",
|
||||||
|
"notifications.policy.filter_bots_hint": "Aðgangar merktir fyrir yrki",
|
||||||
|
"notifications.policy.filter_bots_title": "Yrki",
|
||||||
"notifications.policy.filter_hint": "Senda í pósthólf fyrir síaðar tilkynningar",
|
"notifications.policy.filter_hint": "Senda í pósthólf fyrir síaðar tilkynningar",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Takmarkað af umsjónarmönnum netþjóns",
|
"notifications.policy.filter_limited_accounts_hint": "Takmarkað af umsjónarmönnum netþjóns",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Aðgangar í umsjón",
|
"notifications.policy.filter_limited_accounts_title": "Aðgangar í umsjón",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Visualizza aggiornamenti",
|
"home.pending_critical_update.link": "Visualizza aggiornamenti",
|
||||||
"home.pending_critical_update.title": "Aggiornamento critico di sicurezza disponibile!",
|
"home.pending_critical_update.title": "Aggiornamento critico di sicurezza disponibile!",
|
||||||
"home.show_announcements": "Mostra annunci",
|
"home.show_announcements": "Mostra annunci",
|
||||||
|
"ignore_notifications_modal.bots_title": "Ignorare le notifiche dai bot?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon non può informare gli utenti che hai ignorato le loro notifiche. Ignorare le notifiche non impedirà l'invio dei messaggi stessi.",
|
"ignore_notifications_modal.disclaimer": "Mastodon non può informare gli utenti che hai ignorato le loro notifiche. Ignorare le notifiche non impedirà l'invio dei messaggi stessi.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtra invece",
|
"ignore_notifications_modal.filter_instead": "Filtra invece",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Potrai comunque accettare, rifiutare o segnalare gli utenti",
|
"ignore_notifications_modal.filter_to_act_users": "Potrai comunque accettare, rifiutare o segnalare gli utenti",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignora",
|
"notifications.policy.drop": "Ignora",
|
||||||
"notifications.policy.drop_hint": "Scarta definitivamente, per non essere mai più visto",
|
"notifications.policy.drop_hint": "Scarta definitivamente, per non essere mai più visto",
|
||||||
"notifications.policy.filter": "Filtrare",
|
"notifications.policy.filter": "Filtrare",
|
||||||
|
"notifications.policy.filter_bots_hint": "Account contrassegnati come automatizzati",
|
||||||
|
"notifications.policy.filter_bots_title": "Bot",
|
||||||
"notifications.policy.filter_hint": "Invia alla casella in arrivo delle notifiche filtrate",
|
"notifications.policy.filter_hint": "Invia alla casella in arrivo delle notifiche filtrate",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitato dai moderatori del server",
|
"notifications.policy.filter_limited_accounts_hint": "Limitato dai moderatori del server",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Account moderati",
|
"notifications.policy.filter_limited_accounts_title": "Account moderati",
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
"account.featured": "Wyróżnione",
|
"account.featured": "Wyróżnione",
|
||||||
"account.featured.accounts": "Profile",
|
"account.featured.accounts": "Profile",
|
||||||
"account.featured.collections": "Kolekcje",
|
"account.featured.collections": "Kolekcje",
|
||||||
|
"account.featured.new_collection": "Nowa kolekcja",
|
||||||
"account.field_overflow": "Pokaż całą zawartość",
|
"account.field_overflow": "Pokaż całą zawartość",
|
||||||
"account.filters.all": "Wszystkie aktywności",
|
"account.filters.all": "Wszystkie aktywności",
|
||||||
"account.filters.boosts_toggle": "Pokaż ulepszenia",
|
"account.filters.boosts_toggle": "Pokaż ulepszenia",
|
||||||
@ -68,9 +69,13 @@
|
|||||||
"account.go_to_profile": "Przejdź do profilu",
|
"account.go_to_profile": "Przejdź do profilu",
|
||||||
"account.hide_reblogs": "Ukryj podbicia od @{name}",
|
"account.hide_reblogs": "Ukryj podbicia od @{name}",
|
||||||
"account.in_memoriam": "Ku pamięci.",
|
"account.in_memoriam": "Ku pamięci.",
|
||||||
|
"account.join_modal.day": "Dzień",
|
||||||
"account.join_modal.me": "Dołączyłeś(aś) na {server}",
|
"account.join_modal.me": "Dołączyłeś(aś) na {server}",
|
||||||
"account.join_modal.me_today": "To Twój pierwszy dzień na {server}!",
|
"account.join_modal.me_today": "To Twój pierwszy dzień na {server}!",
|
||||||
"account.join_modal.other": "{name} dołączył(a) na {server}",
|
"account.join_modal.other": "{name} dołączył(a) na {server}",
|
||||||
|
"account.join_modal.share.celebrate": "Udostępnij uroczysty post",
|
||||||
|
"account.join_modal.share.intro": "Udostępnij post wprowadzający",
|
||||||
|
"account.join_modal.share.welcome": "Udostępnij post powitalny",
|
||||||
"account.joined_short": "Dołączył(a)",
|
"account.joined_short": "Dołączył(a)",
|
||||||
"account.languages": "Zmień subskrybowane języki",
|
"account.languages": "Zmień subskrybowane języki",
|
||||||
"account.last_active": "Ostatnia aktywność",
|
"account.last_active": "Ostatnia aktywność",
|
||||||
@ -120,6 +125,7 @@
|
|||||||
"account.note.edit_button": "Edytuj",
|
"account.note.edit_button": "Edytuj",
|
||||||
"account.note.title": "Osobista notatka (widoczna tylko dla Ciebie)",
|
"account.note.title": "Osobista notatka (widoczna tylko dla Ciebie)",
|
||||||
"account.open_original_page": "Otwórz stronę oryginalną",
|
"account.open_original_page": "Otwórz stronę oryginalną",
|
||||||
|
"account.pending": "Oczekuje",
|
||||||
"account.posts": "Wpisy",
|
"account.posts": "Wpisy",
|
||||||
"account.remove_from_followers": "Usuń {name} z obserwujących",
|
"account.remove_from_followers": "Usuń {name} z obserwujących",
|
||||||
"account.report": "Zgłoś @{name}",
|
"account.report": "Zgłoś @{name}",
|
||||||
@ -173,9 +179,12 @@
|
|||||||
"account_edit.field_edit_modal.discard_confirm": "Odrzuć",
|
"account_edit.field_edit_modal.discard_confirm": "Odrzuć",
|
||||||
"account_edit.field_edit_modal.discard_message": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?",
|
"account_edit.field_edit_modal.discard_message": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?",
|
||||||
"account_edit.field_edit_modal.edit_title": "Edytuj dodatkowe pole",
|
"account_edit.field_edit_modal.edit_title": "Edytuj dodatkowe pole",
|
||||||
|
"account_edit.field_edit_modal.name_hint": "Np. Strona osobista",
|
||||||
"account_edit.field_edit_modal.name_label": "Etykieta",
|
"account_edit.field_edit_modal.name_label": "Etykieta",
|
||||||
"account_edit.field_edit_modal.value_label": "Wartość",
|
"account_edit.field_edit_modal.value_label": "Wartość",
|
||||||
"account_edit.image_alt_modal.add_title": "Dodaj tekst alternatywny",
|
"account_edit.image_alt_modal.add_title": "Dodaj tekst alternatywny",
|
||||||
|
"account_edit.image_alt_modal.edit_title": "Edytuj tekst alternatywny",
|
||||||
|
"account_edit.image_alt_modal.text_hint": "Tekst alternatywny pomaga użytkownikom czytników ekranu zrozumieć twoją treść.",
|
||||||
"account_edit.image_alt_modal.text_label": "Tekst alternatywny",
|
"account_edit.image_alt_modal.text_label": "Tekst alternatywny",
|
||||||
"account_edit.image_delete_modal.delete_button": "Usuń",
|
"account_edit.image_delete_modal.delete_button": "Usuń",
|
||||||
"account_edit.image_delete_modal.title": "Usunąć obraz?",
|
"account_edit.image_delete_modal.title": "Usunąć obraz?",
|
||||||
@ -185,6 +194,8 @@
|
|||||||
"account_edit.image_edit.remove_button": "Usuń obraz",
|
"account_edit.image_edit.remove_button": "Usuń obraz",
|
||||||
"account_edit.image_edit.replace_button": "Zastąp obraz",
|
"account_edit.image_edit.replace_button": "Zastąp obraz",
|
||||||
"account_edit.item_list.delete": "Usuń {name}",
|
"account_edit.item_list.delete": "Usuń {name}",
|
||||||
|
"account_edit.name_modal.add_title": "Dodaj nazwę wyświetlaną",
|
||||||
|
"account_edit.name_modal.edit_title": "Edytuj nazwę wyświetlaną",
|
||||||
"account_edit.profile_tab.button_label": "Dostosuj",
|
"account_edit.profile_tab.button_label": "Dostosuj",
|
||||||
"account_edit.profile_tab.hint.description": "Te ustawienia wpływają na to, co użytkownicy widzą na {server} w oficjalnych aplikacjach, ale mogą nie obowiązywać na innych serwerach i w aplikacjach zewnętrznych.",
|
"account_edit.profile_tab.hint.description": "Te ustawienia wpływają na to, co użytkownicy widzą na {server} w oficjalnych aplikacjach, ale mogą nie obowiązywać na innych serwerach i w aplikacjach zewnętrznych.",
|
||||||
"account_edit.profile_tab.hint.title": "Wyświetlanie może się różnić",
|
"account_edit.profile_tab.hint.title": "Wyświetlanie może się różnić",
|
||||||
@ -204,12 +215,14 @@
|
|||||||
"account_edit.upload_modal.next": "Następne",
|
"account_edit.upload_modal.next": "Następne",
|
||||||
"account_edit.upload_modal.step_crop.zoom": "Powiększenie",
|
"account_edit.upload_modal.step_crop.zoom": "Powiększenie",
|
||||||
"account_edit.upload_modal.step_upload.button": "Wybierz pliki",
|
"account_edit.upload_modal.step_upload.button": "Wybierz pliki",
|
||||||
|
"account_edit.upload_modal.step_upload.dragging": "Upuść, aby przesłać",
|
||||||
"account_edit.upload_modal.step_upload.header": "Wybierz obraz",
|
"account_edit.upload_modal.step_upload.header": "Wybierz obraz",
|
||||||
"account_edit.upload_modal.step_upload.hint": "WEBP, PNG, GIF lub JPG, maksymalnie {limit} MB.{br}Obraz zostanie przeskalowany do {width}x{height} px.",
|
"account_edit.upload_modal.step_upload.hint": "WEBP, PNG, GIF lub JPG, maksymalnie {limit} MB.{br}Obraz zostanie przeskalowany do {width}x{height} px.",
|
||||||
"account_edit.upload_modal.title_add.avatar": "Dodaj zdjęcie profilowe",
|
"account_edit.upload_modal.title_add.avatar": "Dodaj zdjęcie profilowe",
|
||||||
"account_edit.upload_modal.title_add.header": "Dodaj zdjęcie nagłówka",
|
"account_edit.upload_modal.title_add.header": "Dodaj zdjęcie nagłówka",
|
||||||
"account_edit.upload_modal.title_replace.avatar": "Zmień zdjęcie profilowe",
|
"account_edit.upload_modal.title_replace.avatar": "Zmień zdjęcie profilowe",
|
||||||
"account_edit.upload_modal.title_replace.header": "Zmień zdjęcie nagłówka",
|
"account_edit.upload_modal.title_replace.header": "Zmień zdjęcie nagłówka",
|
||||||
|
"account_edit.verified_modal.invisible_link.summary": "Co zrobić, aby link był niewidoczny?",
|
||||||
"account_edit_tags.add_tag": "Dodaj #{tagName}",
|
"account_edit_tags.add_tag": "Dodaj #{tagName}",
|
||||||
"account_edit_tags.search_placeholder": "Dodaj hashtag...",
|
"account_edit_tags.search_placeholder": "Dodaj hashtag...",
|
||||||
"admin.dashboard.daily_retention": "Wskaźnik utrzymania użytkowników według dni od rejestracji",
|
"admin.dashboard.daily_retention": "Wskaźnik utrzymania użytkowników według dni od rejestracji",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Ver atualizações",
|
"home.pending_critical_update.link": "Ver atualizações",
|
||||||
"home.pending_critical_update.title": "Atualização de segurança crítica disponível!",
|
"home.pending_critical_update.title": "Atualização de segurança crítica disponível!",
|
||||||
"home.show_announcements": "Mostrar anúncios",
|
"home.show_announcements": "Mostrar anúncios",
|
||||||
|
"ignore_notifications_modal.bots_title": "Ignorar notificações de robôs?",
|
||||||
"ignore_notifications_modal.disclaimer": "O Mastodon não informa os usuários se você ignorar as notificações deles. Ignorar notificações não impedirá as mensagens de serem enviadas.",
|
"ignore_notifications_modal.disclaimer": "O Mastodon não informa os usuários se você ignorar as notificações deles. Ignorar notificações não impedirá as mensagens de serem enviadas.",
|
||||||
"ignore_notifications_modal.filter_instead": "Filtrar em vez disso",
|
"ignore_notifications_modal.filter_instead": "Filtrar em vez disso",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Você ainda poderá aceitar, rejeitar ou denunciar",
|
"ignore_notifications_modal.filter_to_act_users": "Você ainda poderá aceitar, rejeitar ou denunciar",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Ignorar",
|
"notifications.policy.drop": "Ignorar",
|
||||||
"notifications.policy.drop_hint": "Enviar para o vácuo, para nunca mais ser visto",
|
"notifications.policy.drop_hint": "Enviar para o vácuo, para nunca mais ser visto",
|
||||||
"notifications.policy.filter": "Filtrar",
|
"notifications.policy.filter": "Filtrar",
|
||||||
|
"notifications.policy.filter_bots_hint": "Contas marcadas como automáticas",
|
||||||
|
"notifications.policy.filter_bots_title": "Robôs",
|
||||||
"notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas",
|
"notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor",
|
"notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Contas moderadas",
|
"notifications.policy.filter_limited_accounts_title": "Contas moderadas",
|
||||||
|
|||||||
@ -841,6 +841,7 @@
|
|||||||
"keyboard_shortcuts.toggle_sensitivity": "Për shfaqje/fshehje mediash",
|
"keyboard_shortcuts.toggle_sensitivity": "Për shfaqje/fshehje mediash",
|
||||||
"keyboard_shortcuts.toot": "Për të filluar një mesazh të ri",
|
"keyboard_shortcuts.toot": "Për të filluar një mesazh të ri",
|
||||||
"keyboard_shortcuts.top": "Shpjere në krye të listës",
|
"keyboard_shortcuts.top": "Shpjere në krye të listës",
|
||||||
|
"keyboard_shortcuts.translate": "Përkthe një postim",
|
||||||
"keyboard_shortcuts.unfocus": "Për heqjen e fokusit nga fusha e hartimit të mesazheve apo kërkimeve",
|
"keyboard_shortcuts.unfocus": "Për heqjen e fokusit nga fusha e hartimit të mesazheve apo kërkimeve",
|
||||||
"keyboard_shortcuts.up": "Për ngjitje sipër nëpër listë",
|
"keyboard_shortcuts.up": "Për ngjitje sipër nëpër listë",
|
||||||
"learn_more_link.got_it": "E mora vesh",
|
"learn_more_link.got_it": "E mora vesh",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "Xem bản cập nhật",
|
"home.pending_critical_update.link": "Xem bản cập nhật",
|
||||||
"home.pending_critical_update.title": "Có bản cập nhật bảo mật quan trọng!",
|
"home.pending_critical_update.title": "Có bản cập nhật bảo mật quan trọng!",
|
||||||
"home.show_announcements": "Xem thông báo máy chủ",
|
"home.show_announcements": "Xem thông báo máy chủ",
|
||||||
|
"ignore_notifications_modal.bots_title": "Bỏ qua thông báo từ các tài khoản bot?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon sẽ không thông báo cho tài khoản rằng bạn đã bỏ qua thông báo của họ. Họ sẽ vẫn có thể tương tác với bạn.",
|
"ignore_notifications_modal.disclaimer": "Mastodon sẽ không thông báo cho tài khoản rằng bạn đã bỏ qua thông báo của họ. Họ sẽ vẫn có thể tương tác với bạn.",
|
||||||
"ignore_notifications_modal.filter_instead": "Lọc thay thế",
|
"ignore_notifications_modal.filter_instead": "Lọc thay thế",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Bạn vẫn có thể chấp nhận, từ chối hoặc báo cáo tài khoản khác",
|
"ignore_notifications_modal.filter_to_act_users": "Bạn vẫn có thể chấp nhận, từ chối hoặc báo cáo tài khoản khác",
|
||||||
@ -846,6 +847,7 @@
|
|||||||
"keyboard_shortcuts.toggle_sensitivity": "ẩn/hiện ảnh hoặc video",
|
"keyboard_shortcuts.toggle_sensitivity": "ẩn/hiện ảnh hoặc video",
|
||||||
"keyboard_shortcuts.toot": "soạn tút mới",
|
"keyboard_shortcuts.toot": "soạn tút mới",
|
||||||
"keyboard_shortcuts.top": "di chuyển đến đầu danh sách",
|
"keyboard_shortcuts.top": "di chuyển đến đầu danh sách",
|
||||||
|
"keyboard_shortcuts.translate": "Dịch tút",
|
||||||
"keyboard_shortcuts.unfocus": "đưa con trỏ ra khỏi ô soạn thảo hoặc ô tìm kiếm",
|
"keyboard_shortcuts.unfocus": "đưa con trỏ ra khỏi ô soạn thảo hoặc ô tìm kiếm",
|
||||||
"keyboard_shortcuts.up": "di chuyển lên trên danh sách",
|
"keyboard_shortcuts.up": "di chuyển lên trên danh sách",
|
||||||
"learn_more_link.got_it": "Đã hiểu",
|
"learn_more_link.got_it": "Đã hiểu",
|
||||||
@ -1041,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "Bỏ qua",
|
"notifications.policy.drop": "Bỏ qua",
|
||||||
"notifications.policy.drop_hint": "Bỏ qua vĩnh viễn",
|
"notifications.policy.drop_hint": "Bỏ qua vĩnh viễn",
|
||||||
"notifications.policy.filter": "Lọc",
|
"notifications.policy.filter": "Lọc",
|
||||||
|
"notifications.policy.filter_bots_hint": "Những tài khoản đánh dấu là tự động",
|
||||||
|
"notifications.policy.filter_bots_title": "Bot",
|
||||||
"notifications.policy.filter_hint": "Cho vào mục thông báo đã lọc",
|
"notifications.policy.filter_hint": "Cho vào mục thông báo đã lọc",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Chỉ dành cho kiểm duyệt viên",
|
"notifications.policy.filter_limited_accounts_hint": "Chỉ dành cho kiểm duyệt viên",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Kiểm duyệt tài khoản",
|
"notifications.policy.filter_limited_accounts_title": "Kiểm duyệt tài khoản",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "查看更新",
|
"home.pending_critical_update.link": "查看更新",
|
||||||
"home.pending_critical_update.title": "有紧急安全更新!",
|
"home.pending_critical_update.title": "有紧急安全更新!",
|
||||||
"home.show_announcements": "显示公告",
|
"home.show_announcements": "显示公告",
|
||||||
|
"ignore_notifications_modal.bots_title": "是否忽略来自机器人账号的通知?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon无法通知对方用户你忽略了他们的通知。忽略通知不会阻止消息本身的发送。",
|
"ignore_notifications_modal.disclaimer": "Mastodon无法通知对方用户你忽略了他们的通知。忽略通知不会阻止消息本身的发送。",
|
||||||
"ignore_notifications_modal.filter_instead": "改为过滤",
|
"ignore_notifications_modal.filter_instead": "改为过滤",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "你仍然可以接受、拒绝或举报用户",
|
"ignore_notifications_modal.filter_to_act_users": "你仍然可以接受、拒绝或举报用户",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "忽略",
|
"notifications.policy.drop": "忽略",
|
||||||
"notifications.policy.drop_hint": "送入虚空,再也不查看",
|
"notifications.policy.drop_hint": "送入虚空,再也不查看",
|
||||||
"notifications.policy.filter": "过滤",
|
"notifications.policy.filter": "过滤",
|
||||||
|
"notifications.policy.filter_bots_hint": "被标记为自动化的账号",
|
||||||
|
"notifications.policy.filter_bots_title": "机器人",
|
||||||
"notifications.policy.filter_hint": "发送到被过滤通知列表",
|
"notifications.policy.filter_hint": "发送到被过滤通知列表",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "被服务器管理员限制的账号",
|
"notifications.policy.filter_limited_accounts_hint": "被服务器管理员限制的账号",
|
||||||
"notifications.policy.filter_limited_accounts_title": "受限账号",
|
"notifications.policy.filter_limited_accounts_title": "受限账号",
|
||||||
|
|||||||
@ -780,6 +780,7 @@
|
|||||||
"home.pending_critical_update.link": "檢視更新內容",
|
"home.pending_critical_update.link": "檢視更新內容",
|
||||||
"home.pending_critical_update.title": "有可取得的重要安全性更新!",
|
"home.pending_critical_update.title": "有可取得的重要安全性更新!",
|
||||||
"home.show_announcements": "顯示公告",
|
"home.show_announcements": "顯示公告",
|
||||||
|
"ignore_notifications_modal.bots_title": "是否忽略來自機器人帳號之推播通知?",
|
||||||
"ignore_notifications_modal.disclaimer": "Mastodon 無法通知您已忽略推播通知之使用者。忽略通知不會阻止訊息本身的發送。",
|
"ignore_notifications_modal.disclaimer": "Mastodon 無法通知您已忽略推播通知之使用者。忽略通知不會阻止訊息本身的發送。",
|
||||||
"ignore_notifications_modal.filter_instead": "改為過濾",
|
"ignore_notifications_modal.filter_instead": "改為過濾",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "您仍能接受、拒絕、或檢舉使用者",
|
"ignore_notifications_modal.filter_to_act_users": "您仍能接受、拒絕、或檢舉使用者",
|
||||||
@ -1042,6 +1043,8 @@
|
|||||||
"notifications.policy.drop": "忽略",
|
"notifications.policy.drop": "忽略",
|
||||||
"notifications.policy.drop_hint": "送至黑洞,永不相見",
|
"notifications.policy.drop_hint": "送至黑洞,永不相見",
|
||||||
"notifications.policy.filter": "過濾器",
|
"notifications.policy.filter": "過濾器",
|
||||||
|
"notifications.policy.filter_bots_hint": "自動化帳號",
|
||||||
|
"notifications.policy.filter_bots_title": "機器人",
|
||||||
"notifications.policy.filter_hint": "送至已過濾推播通知收件夾",
|
"notifications.policy.filter_hint": "送至已過濾推播通知收件夾",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "已被伺服器管理員限制",
|
"notifications.policy.filter_limited_accounts_hint": "已被伺服器管理員限制",
|
||||||
"notifications.policy.filter_limited_accounts_title": "受管制帳號",
|
"notifications.policy.filter_limited_accounts_title": "受管制帳號",
|
||||||
|
|||||||
@ -155,6 +155,10 @@ function appendMedia(state, media, file) {
|
|||||||
|
|
||||||
if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
|
if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
|
||||||
map.set('sensitive', true);
|
map.set('sensitive', true);
|
||||||
|
|
||||||
|
if (state.get('default_sensitive')) {
|
||||||
|
map.set('spoiler', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -400,7 +404,7 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
map.set('spoiler', !state.get('spoiler'));
|
map.set('spoiler', !state.get('spoiler'));
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
|
|
||||||
if (state.get('media_attachments').size >= 1 && !state.get('default_sensitive')) {
|
if (state.get('media_attachments').size >= 1) {
|
||||||
map.set('sensitive', !state.get('spoiler'));
|
map.set('sensitive', !state.get('spoiler'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -545,7 +549,7 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
map.set('spoiler', true);
|
map.set('spoiler', true);
|
||||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||||
} else {
|
} else {
|
||||||
map.set('spoiler', false);
|
map.set('spoiler', action.status.get('sensitive') && action.status.get('media_attachments').size > 0);
|
||||||
map.set('spoiler_text', '');
|
map.set('spoiler_text', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,7 +586,7 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
map.set('spoiler', true);
|
map.set('spoiler', true);
|
||||||
map.set('spoiler_text', action.spoiler_text);
|
map.set('spoiler_text', action.spoiler_text);
|
||||||
} else {
|
} else {
|
||||||
map.set('spoiler', false);
|
map.set('spoiler', action.status.get('sensitive') && action.status.get('media_attachments').size > 0);
|
||||||
map.set('spoiler_text', '');
|
map.set('spoiler_text', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,10 @@ const Modal = ImmutableRecord<Modal>({
|
|||||||
modalProps: ImmutableRecord({})(),
|
modalProps: ImmutableRecord({})(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const IGNORE_FOCUS_ON_OPEN = 'on-open';
|
||||||
|
|
||||||
interface ModalState {
|
interface ModalState {
|
||||||
ignoreFocus: boolean;
|
ignoreFocus: boolean | typeof IGNORE_FOCUS_ON_OPEN;
|
||||||
stack: Stack<ImmutableRecord<Modal>>;
|
stack: Stack<ImmutableRecord<Modal>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +55,10 @@ const pushModal = (
|
|||||||
modalType: ModalType,
|
modalType: ModalType,
|
||||||
modalProps: ModalProps,
|
modalProps: ModalProps,
|
||||||
previousModalProps?: ModalProps,
|
previousModalProps?: ModalProps,
|
||||||
|
ignoreFocusOnOpen = false,
|
||||||
): State => {
|
): State => {
|
||||||
return state.withMutations((record) => {
|
return state.withMutations((record) => {
|
||||||
record.set('ignoreFocus', false);
|
record.set('ignoreFocus', ignoreFocusOnOpen ? IGNORE_FOCUS_ON_OPEN : false);
|
||||||
record.update('stack', (stack) => {
|
record.update('stack', (stack) => {
|
||||||
let tmp = stack;
|
let tmp = stack;
|
||||||
|
|
||||||
@ -92,6 +95,7 @@ export const modalReducer: Reducer<State> = (state = initialState, action) => {
|
|||||||
action.payload.modalType,
|
action.payload.modalType,
|
||||||
action.payload.modalProps,
|
action.payload.modalProps,
|
||||||
action.payload.previousModalProps,
|
action.payload.previousModalProps,
|
||||||
|
action.payload.ignoreFocus,
|
||||||
);
|
);
|
||||||
else if (closeModal.match(action)) return popModal(state, action.payload);
|
else if (closeModal.match(action)) return popModal(state, action.payload);
|
||||||
// TODO: type those actions
|
// TODO: type those actions
|
||||||
|
|||||||
@ -40,11 +40,13 @@ describe('createCache', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('removes oldest item cached if it exceeds a set size', () => {
|
test('removes oldest item cached if it exceeds a set size', () => {
|
||||||
const cache = createLimitedCache({ maxSize: 1 });
|
const cache = createLimitedCache({ maxSize: 2 });
|
||||||
cache.set('test1', 1);
|
cache.set('test1', 1);
|
||||||
cache.set('test2', 2);
|
cache.set('test2', 2);
|
||||||
|
cache.set('test3', 3);
|
||||||
expect(cache.get('test1')).toBeUndefined();
|
expect(cache.get('test1')).toBeUndefined();
|
||||||
expect(cache.get('test2')).toBe(2);
|
expect(cache.get('test2')).toBe(2);
|
||||||
|
expect(cache.get('test3')).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('retrieving a value bumps up last access', () => {
|
test('retrieving a value bumps up last access', () => {
|
||||||
@ -63,13 +65,13 @@ describe('createCache', () => {
|
|||||||
const cache = createLimitedCache({ maxSize: 1, log });
|
const cache = createLimitedCache({ maxSize: 1, log });
|
||||||
cache.set('test1', 1);
|
cache.set('test1', 1);
|
||||||
expect(log).toHaveBeenLastCalledWith(
|
expect(log).toHaveBeenLastCalledWith(
|
||||||
'Added %s to cache, now size %d',
|
'Added %o to cache, now size %d',
|
||||||
'test1',
|
'test1',
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
cache.set('test2', 1);
|
cache.set('test2', 1);
|
||||||
expect(log).toHaveBeenLastCalledWith(
|
expect(log).toHaveBeenLastCalledWith(
|
||||||
'Added %s and deleted %s from cache, now size %d',
|
'Added %o and deleted %o from cache, now size %d',
|
||||||
'test2',
|
'test2',
|
||||||
'test1',
|
'test1',
|
||||||
1,
|
1,
|
||||||
|
|||||||
@ -43,13 +43,13 @@ export function createLimitedCache<CacheValue, CacheKey = string>({
|
|||||||
cacheMap.delete(lastKey);
|
cacheMap.delete(lastKey);
|
||||||
cacheKeys.delete(lastKey);
|
cacheKeys.delete(lastKey);
|
||||||
log(
|
log(
|
||||||
'Added %s and deleted %s from cache, now size %d',
|
'Added %o and deleted %o from cache, now size %d',
|
||||||
key,
|
key,
|
||||||
lastKey,
|
lastKey,
|
||||||
cacheMap.size,
|
cacheMap.size,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log('Added %s to cache, now size %d', key, cacheMap.size);
|
log('Added %o to cache, now size %d', key, cacheMap.size);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clear: () => {
|
clear: () => {
|
||||||
|
|||||||
@ -3590,6 +3590,8 @@ a.account__display-name {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 32px;
|
padding-bottom: 32px;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -4641,6 +4643,7 @@ a.status-card {
|
|||||||
&__title-wrapper {
|
&__title-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
@ -4656,9 +4659,7 @@ a.status-card {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
text-overflow: ellipsis;
|
min-width: 0;
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&--with-back-button {
|
&--with-back-button {
|
||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
@ -4673,6 +4674,12 @@ a.status-card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.column-header__back-button {
|
.column-header__back-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: var(--color-text-brand);
|
color: var(--color-text-brand);
|
||||||
@ -9141,6 +9148,8 @@ noscript {
|
|||||||
padding-bottom: 32px;
|
padding-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
line-height: 33px;
|
line-height: 33px;
|
||||||
@ -9171,6 +9180,8 @@ noscript {
|
|||||||
&__lead {
|
&__lead {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
@ -9246,6 +9257,8 @@ noscript {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,8 +39,8 @@ class ReportFilter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def status_scope
|
def status_scope
|
||||||
resolved = params.key?(:resolved)
|
resolved = ActiveModel::Type::Boolean.new.cast(params[:resolved])
|
||||||
unresolved = params.key?(:unresolved)
|
unresolved = ActiveModel::Type::Boolean.new.cast(params[:unresolved])
|
||||||
|
|
||||||
return Report.all if resolved && unresolved
|
return Report.all if resolved && unresolved
|
||||||
return Report.resolved if resolved
|
return Report.resolved if resolved
|
||||||
|
|||||||
@ -44,6 +44,16 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class ImageWithDescription < SimpleDelegator
|
||||||
|
attr_reader :description
|
||||||
|
|
||||||
|
def initialize(object, description)
|
||||||
|
super(object)
|
||||||
|
|
||||||
|
@description = description
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
has_one :endpoints, serializer: EndpointsSerializer
|
has_one :endpoints, serializer: EndpointsSerializer
|
||||||
|
|
||||||
has_one :icon, serializer: ActivityPub::ImageSerializer, if: :avatar_exists?
|
has_one :icon, serializer: ActivityPub::ImageSerializer, if: :avatar_exists?
|
||||||
@ -120,11 +130,11 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def icon
|
def icon
|
||||||
object.avatar
|
ImageWithDescription.new(object.avatar, object.avatar_description)
|
||||||
end
|
end
|
||||||
|
|
||||||
def image
|
def image
|
||||||
object.header
|
ImageWithDescription.new(object.header, object.header_description)
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_key
|
def public_key
|
||||||
|
|||||||
@ -7,6 +7,7 @@ class ActivityPub::ImageSerializer < ActivityPub::Serializer
|
|||||||
|
|
||||||
attributes :type, :media_type, :url
|
attributes :type, :media_type, :url
|
||||||
attribute :focal_point, if: :focal_point?
|
attribute :focal_point, if: :focal_point?
|
||||||
|
attribute :summary, if: :summary?
|
||||||
|
|
||||||
def type
|
def type
|
||||||
'Image'
|
'Image'
|
||||||
@ -27,4 +28,12 @@ class ActivityPub::ImageSerializer < ActivityPub::Serializer
|
|||||||
def focal_point
|
def focal_point
|
||||||
[object.meta['focus']['x'], object.meta['focus']['y']]
|
[object.meta['focus']['x'], object.meta['focus']['y']]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def summary?
|
||||||
|
object.respond_to?(:description) && object.description.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def summary
|
||||||
|
object.description
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -206,7 +206,7 @@ class PostStatusService < BaseService
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT || @options[:poll].present?
|
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT
|
||||||
|
|
||||||
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i))
|
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i))
|
||||||
|
|
||||||
|
|||||||
@ -23,12 +23,15 @@ class ProcessLinksService < BaseService
|
|||||||
def scan_text!
|
def scan_text!
|
||||||
urls = @status.text.scan(FetchLinkCardService::URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize }
|
urls = @status.text.scan(FetchLinkCardService::URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize }
|
||||||
|
|
||||||
|
domains = urls.map(&:domain).uniq
|
||||||
|
valid_domains = Instance.searchable.where(domain: domains).pluck(:domain)
|
||||||
|
|
||||||
urls.each do |url|
|
urls.each do |url|
|
||||||
# We only support `FeaturedCollection` at this time
|
# We only support `FeaturedCollection` at this time
|
||||||
|
|
||||||
# TODO: We probably want to resolve unknown objects at authoring time
|
|
||||||
object = ActivityPub::TagManager.instance.uri_to_resource(url.to_s, Collection)
|
object = ActivityPub::TagManager.instance.uri_to_resource(url.to_s, Collection)
|
||||||
next if object.nil?
|
object ||= ResolveURLService.new.call(url.to_s) if valid_domains.include?(url.domain)
|
||||||
|
next unless object.is_a?(Collection)
|
||||||
|
|
||||||
tagged_object = @previous_objects.find { |x| x.object == object || x.uri == url }
|
tagged_object = @previous_objects.find { |x| x.object == object || x.uri == url }
|
||||||
tagged_object ||= @current_objects.find { |x| x.object == object || x.uri == url }
|
tagged_object ||= @current_objects.find { |x| x.object == object || x.uri == url }
|
||||||
|
|||||||
20
app/views/collections/show.html.haml
Normal file
20
app/views/collections/show.html.haml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
- content_for :page_title, @collection.name
|
||||||
|
|
||||||
|
- content_for :header_tags do
|
||||||
|
%meta{ name: 'robots', content: 'noindex, noarchive' }/
|
||||||
|
|
||||||
|
%link{ rel: 'alternate', type: 'application/activity+json', href: ap_account_collection_url(@collection.account_id, @collection) }/
|
||||||
|
%link{ rel: 'alternate', type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', href: ap_account_collection_url(@collection.account_id, @collection) }/
|
||||||
|
|
||||||
|
- unless @collection.sensitive?
|
||||||
|
%meta{ name: 'description', content: @collection.description }/
|
||||||
|
= opengraph 'og:description', @collection.description
|
||||||
|
= opengraph 'og:site_name', site_title
|
||||||
|
= opengraph 'og:type', 'website'
|
||||||
|
= opengraph 'og:title', @collection.name
|
||||||
|
= opengraph 'og:url', collection_url(@collection)
|
||||||
|
- if @collection.language.present?
|
||||||
|
= opengraph 'og:locale', @collection.language
|
||||||
|
= opengraph 'twitter:card', 'summary'
|
||||||
|
|
||||||
|
= render 'shared/web_app'
|
||||||
@ -525,6 +525,21 @@ be:
|
|||||||
no_lists_yet: Пакуль няма спісаў
|
no_lists_yet: Пакуль няма спісаў
|
||||||
last_email: Апошняя электронная пошта
|
last_email: Апошняя электронная пошта
|
||||||
lead: Уліковыя запісы, якія ўключылі гэту функцыю і маюць падпісчыкаў, будуць паказаныя знізу.
|
lead: Уліковыя запісы, якія ўключылі гэту функцыю і маюць падпісчыкаў, будуць паказаныя знізу.
|
||||||
|
show:
|
||||||
|
confirm_disable_feature: Адключыць рассылку па электроннай пошце для %{name}? Абнаўленні больш не будуць прыходзіць на пошту гэтага ўліковага запісу. Карыстальнік усё яшчэ зможа ўключыць гэту функцыю нанова ў наладах уліковага запісу. Каб назаўсёды прыбраць доступ да гэтай функцыі, змяніце дазволы ўліковага запісу ў Ролях.
|
||||||
|
confirm_remove_subscriber: "%{email} больш не будзе атрымліваць электронныя лісты ад %{name}. Гэтае дзеянне незваротнае."
|
||||||
|
consent: Падпісчыкі пагадзіліся толькі атрымліваць допісы на электронную пошту. Не карыстайцеся гэтым спісам у іншых мэтах.
|
||||||
|
date: Дата рэгістрацыі
|
||||||
|
disable_feature: Адключыць функцыю
|
||||||
|
disabled: Функцыя была адключаная і электронныя лісты больш не будуць дасылацца гэтаму спісу.
|
||||||
|
email: Адрас электроннай пошты
|
||||||
|
empty:
|
||||||
|
hint: Ніхто яшчэ не аформіў падпіску на гэты ўліковы запіс.
|
||||||
|
no_subscribers_yet: Пакуль няма падпісчыкаў
|
||||||
|
enable_feature: Уключыць функцыю
|
||||||
|
no_access_html: У гэтага ўліковага запісу больш няма дазволаў, патрэбных, каб уключыць гэту функцыю. Змяніце гэта ў <a href="%{roles_path}">Ролях</a>.
|
||||||
|
title: Паштовая рассылка %{name}
|
||||||
|
view_account: Прагл. уліковы запіс
|
||||||
status: Стан
|
status: Стан
|
||||||
subscribers: Падпісчыкі
|
subscribers: Падпісчыкі
|
||||||
title: Спісы рассылкі
|
title: Спісы рассылкі
|
||||||
@ -1541,6 +1556,7 @@ be:
|
|||||||
your_appeal_rejected: Ваша абскарджанне было адхілена
|
your_appeal_rejected: Ваша абскарджанне было адхілена
|
||||||
edit_profile:
|
edit_profile:
|
||||||
other: Іншае
|
other: Іншае
|
||||||
|
privacy_redesign_body: Уключыць ці адключыць паказ Вашых падпісчыкаў і падпісак цяпер можна прама ў Вашым профілі.
|
||||||
redesign_body: Рэдагаванне профілю цяпер даступнае наўпрост са старонкі профілю.
|
redesign_body: Рэдагаванне профілю цяпер даступнае наўпрост са старонкі профілю.
|
||||||
redesign_button: Перайсці туды
|
redesign_button: Перайсці туды
|
||||||
redesign_title: Адбыліся змены ў рэдагаванні профілю
|
redesign_title: Адбыліся змены ў рэдагаванні профілю
|
||||||
@ -1575,7 +1591,9 @@ be:
|
|||||||
success_html: Вы цяпер пачняце атрымліваць электронныя лісты, калі %{name} будзе рабіць новыя допісы. Дадайце %{sender} у свае кантакты, каб гэтыя допісы не траплялі ў папку са спамам.
|
success_html: Вы цяпер пачняце атрымліваць электронныя лісты, калі %{name} будзе рабіць новыя допісы. Дадайце %{sender} у свае кантакты, каб гэтыя допісы не траплялі ў папку са спамам.
|
||||||
title: Вы падпісаліся праз эл. пошту
|
title: Вы падпісаліся праз эл. пошту
|
||||||
unsubscribe: Адпісацца
|
unsubscribe: Адпісацца
|
||||||
|
disabled: Адключана
|
||||||
inactive: Неактыўная
|
inactive: Неактыўная
|
||||||
|
no_access: Няма доступу
|
||||||
status: Стан
|
status: Стан
|
||||||
subscribers: Падпісчыкі па эл.пошце
|
subscribers: Падпісчыкі па эл.пошце
|
||||||
emoji_styles:
|
emoji_styles:
|
||||||
|
|||||||
@ -505,6 +505,21 @@ gl:
|
|||||||
no_lists_yet: Aínda non hai listas
|
no_lists_yet: Aínda non hai listas
|
||||||
last_email: Último correo
|
last_email: Último correo
|
||||||
lead: Aquí móstranse as contas que activaron esta ferramenta e teñen subscritoras.
|
lead: Aquí móstranse as contas que activaron esta ferramenta e teñen subscritoras.
|
||||||
|
show:
|
||||||
|
confirm_disable_feature: Desactivar o boletín para %{name}? Non se van enviar máis novidades desta conta por correo. A usuaria poderá reactivar esta ferramenta nos axustes da súa conta. Para quitar de xeito permanente o acceso á ferramenta, edita os permisos da conta en Roles.
|
||||||
|
confirm_remove_subscriber: "%{email} non vai seguir recibindo correos de %{name}. Esta acción non é reversible."
|
||||||
|
consent: As subscritoras consentiron exclusivamente recibir no correo as publicacións. Non uses esta lista para outros propósitos.
|
||||||
|
date: Data da subscrición
|
||||||
|
disable_feature: Desactivar a ferramenta
|
||||||
|
disabled: Esta ferramenta desactivouse e non se van seguir enviando correos a esta lista.
|
||||||
|
email: Enderezo de correo
|
||||||
|
empty:
|
||||||
|
hint: Aínda non hai subscricións a esta conta.
|
||||||
|
no_subscribers_yet: Sen subscricións
|
||||||
|
enable_feature: Activar a ferramenta
|
||||||
|
no_access_html: Esta conta xa non ten os permisos requeridos para activar a ferramenta. Cambiaos en <a href="%{roles_path}">Roles</a>.
|
||||||
|
title: Boletín por correo de %{name}
|
||||||
|
view_account: Ver conta
|
||||||
status: Estado
|
status: Estado
|
||||||
subscribers: Subscritoras
|
subscribers: Subscritoras
|
||||||
title: Listas de correo
|
title: Listas de correo
|
||||||
@ -1532,7 +1547,9 @@ gl:
|
|||||||
success_html: Vas comezar a recibir correos cando %{name} publique novas publicacións. Engade %{sender} á túa libreta de enderezos para que os correos non vaian directamente ao cartafol de Spam.
|
success_html: Vas comezar a recibir correos cando %{name} publique novas publicacións. Engade %{sender} á túa libreta de enderezos para que os correos non vaian directamente ao cartafol de Spam.
|
||||||
title: Subscribícheste
|
title: Subscribícheste
|
||||||
unsubscribe: Anular subscrición
|
unsubscribe: Anular subscrición
|
||||||
|
disabled: Desactivado
|
||||||
inactive: Inactiva
|
inactive: Inactiva
|
||||||
|
no_access: Sen acceso
|
||||||
status: Estado
|
status: Estado
|
||||||
subscribers: Subscritoras
|
subscribers: Subscritoras
|
||||||
emoji_styles:
|
emoji_styles:
|
||||||
|
|||||||
@ -525,6 +525,21 @@ he:
|
|||||||
no_lists_yet: אין רשימות עדיין
|
no_lists_yet: אין רשימות עדיין
|
||||||
last_email: הודעת דואל אחרונה
|
last_email: הודעת דואל אחרונה
|
||||||
lead: חשבונות שבהם הופעלה האפשרות ויש להם מנויים יופיעו להלן.
|
lead: חשבונות שבהם הופעלה האפשרות ויש להם מנויים יופיעו להלן.
|
||||||
|
show:
|
||||||
|
confirm_disable_feature: להשבית מנשרי דוא"ל של %{name}? עדכוני דוא"ל לא ישלחו עוד מטעם חשבון זה. המשתמשת תוכל לאפשר מחדש את התכונה מהעדפות החשבון שלה. כדי לכבות לצמיתות גישה לתכונה הזו, יש לערוך את הרשאות החשבון תחת תפקידים.
|
||||||
|
confirm_remove_subscriber: '%{email} לא יקבלו יותר דוא"ל מאת %{name}. פעולה זו לא בלתי הפיכה.'
|
||||||
|
consent: מנויים הסכימו רק לקבלת הודעות דרך דוא"ל. אין להשתש ברשימה זו לאף מטרה אחרת.
|
||||||
|
date: תאריך ההצטרפות
|
||||||
|
disable_feature: כיבוי תכונה
|
||||||
|
disabled: תכונה זו הושבתה והודעות דוא"ל לא נשלחות יותר אל רשימה זו.
|
||||||
|
email: כתובת דוא"ל
|
||||||
|
empty:
|
||||||
|
hint: אין לחשבון זה נרשמים עדיין.
|
||||||
|
no_subscribers_yet: אין מנויים עדיין
|
||||||
|
enable_feature: אפשר תכונה
|
||||||
|
no_access_html: לחשבון זה אין יותר את ההרשאות הדרושות להפעלת התכונה. שנו זאת תחת <a href="%{roles_path}">תפקידים</a>.
|
||||||
|
title: מנשרי דוא"ל של %{name}
|
||||||
|
view_account: הצג חשבון
|
||||||
status: מצב
|
status: מצב
|
||||||
subscribers: מנויים
|
subscribers: מנויים
|
||||||
title: רשימות תפוצה
|
title: רשימות תפוצה
|
||||||
@ -1576,7 +1591,9 @@ he:
|
|||||||
success_html: מעתה תקבלנה דוא"ל כאשר %{name} יפרסמו הודעות חדשות. הוספנה את %{sender} לאנשי הקשר כדי שההודעות האלו לא יגיעו אל פח הספאם שלכן.
|
success_html: מעתה תקבלנה דוא"ל כאשר %{name} יפרסמו הודעות חדשות. הוספנה את %{sender} לאנשי הקשר כדי שההודעות האלו לא יגיעו אל פח הספאם שלכן.
|
||||||
title: נרשמת
|
title: נרשמת
|
||||||
unsubscribe: ביטול ההרשמה
|
unsubscribe: ביטול ההרשמה
|
||||||
|
disabled: כבוי
|
||||||
inactive: לא פעילים
|
inactive: לא פעילים
|
||||||
|
no_access: אין גישה
|
||||||
status: מצב
|
status: מצב
|
||||||
subscribers: מנויים
|
subscribers: מנויים
|
||||||
emoji_styles:
|
emoji_styles:
|
||||||
|
|||||||
@ -506,13 +506,18 @@ is:
|
|||||||
last_email: Síðasti tölvupóstur
|
last_email: Síðasti tölvupóstur
|
||||||
lead: Aðgangar sem hafa virkjað eiginleikann og eru með áskrifendur munu birtast hér fyrir neðan.
|
lead: Aðgangar sem hafa virkjað eiginleikann og eru með áskrifendur munu birtast hér fyrir neðan.
|
||||||
show:
|
show:
|
||||||
|
confirm_disable_feature: Á að gera óvirkar fréttir í tölvupósti frá %{name}? Færslur verða ekki lengur sendar í tölvupósti frá þessum notandaaðgangi. Notandinn mun samt geta virkjað aftur þennan eiginleika í stillingunum sínum. Til að fjarlægja endanlega aðgang að þessum eiginleika skaltu breyta heimildum aðgangsins í gegnum 'Hlutverk'.
|
||||||
|
confirm_remove_subscriber: "%{email} mun ekki lengur fá tölvupósta frá %{name}. Ekki er hægt að afturkalla þessa aðgerð."
|
||||||
|
consent: Áskrifendur hafa einungis samþykkt að fá sendar færslur í tölvupósti. Ekki nota þennan lista í neinum öðrum tilgangi.
|
||||||
date: Dagsetning skráningar
|
date: Dagsetning skráningar
|
||||||
disable_feature: Gera eiginleika óvirkan
|
disable_feature: Gera eiginleika óvirkan
|
||||||
|
disabled: Eiginleikinn var gerður óvirkur og eru tölvupóstar ekki lengur sendir á þenna lista.
|
||||||
email: Tölvupóstfang
|
email: Tölvupóstfang
|
||||||
empty:
|
empty:
|
||||||
hint: Enginn hefur enn gerst áskrifandi að þessum notandaaðgangi.
|
hint: Enginn hefur enn gerst áskrifandi að þessum notandaaðgangi.
|
||||||
no_subscribers_yet: Engir áskrifendur ennþá
|
no_subscribers_yet: Engir áskrifendur ennþá
|
||||||
enable_feature: Virkja eiginleika
|
enable_feature: Virkja eiginleika
|
||||||
|
no_access_html: Þessi aðgangur hefur ekki lengur heimild til að virkja þennan eiginleika. Breyttu því í <a href="%{roles_path}">Hlutverk</a>.
|
||||||
title: Tölvupóstfréttir frá %{name}
|
title: Tölvupóstfréttir frá %{name}
|
||||||
view_account: Skoða notandaaðgang
|
view_account: Skoða notandaaðgang
|
||||||
status: Staða
|
status: Staða
|
||||||
|
|||||||
@ -505,6 +505,21 @@ sq:
|
|||||||
no_lists_yet: Ende pa lista
|
no_lists_yet: Ende pa lista
|
||||||
last_email: Email-i i fundit
|
last_email: Email-i i fundit
|
||||||
lead: Llogaritë që kanë aktivizuar veçorinë dhe kanë pajtimtarë do të shfaqen më poshtë.
|
lead: Llogaritë që kanë aktivizuar veçorinë dhe kanë pajtimtarë do të shfaqen më poshtë.
|
||||||
|
show:
|
||||||
|
confirm_disable_feature: Të çaktivizohen buletine me email për %{name}? Për këtë llogari s’do të dërgohen më përditësime me email. Përdoruesi do të jetë prapëseprapë në gjendje të riaktivizojë veçorinë që nga rregullimet e llogarisë të vet. Për të hequr përgjithnjë përdorimin e kësaj veçorie, përpunoni lejen e llogarisë te Role.
|
||||||
|
confirm_remove_subscriber: "%{email} s’do të marrë më email-e nga %{name}. Ky veprim s’mund të zhbëhet."
|
||||||
|
consent: Pajtimtarët kanë pranuar vetëm të marrin postime përmes email-i. Mos e përdorni këtë listë për qëllime të tjera.
|
||||||
|
date: Datë e regjistrimit
|
||||||
|
disable_feature: Çaktivizoje veçorinë
|
||||||
|
disabled: Veçoria qe çaktivizuar dhe te kjo listë s’dërgohen më email-e.
|
||||||
|
email: Adresë email
|
||||||
|
empty:
|
||||||
|
hint: Te kjo llogari s’është pajtuar ende dikush.
|
||||||
|
no_subscribers_yet: Ende pa pajtimtarë
|
||||||
|
enable_feature: Aktivizoje veçorinë
|
||||||
|
no_access_html: Kjo llogari s’ka më lejet e domosdoshme për aktivizimin e veçorisë. Ndryshojeni këtë që nga <a href="%{roles_path}">Role</a>.
|
||||||
|
title: Buletine me email nga %{name}
|
||||||
|
view_account: Shiheni llogarinë
|
||||||
status: Gjendje
|
status: Gjendje
|
||||||
subscribers: Pajtimtarë
|
subscribers: Pajtimtarë
|
||||||
title: Lista postimesh
|
title: Lista postimesh
|
||||||
@ -1521,7 +1536,9 @@ sq:
|
|||||||
success_html: Tani do të filloni të merrni email-e, kur %{name} boton postime të reja. Shtojeni %{sender} te kontaktet tuaj, që këto postime të mos përfundojnë te dosja juaj e Të padëshiruarve.
|
success_html: Tani do të filloni të merrni email-e, kur %{name} boton postime të reja. Shtojeni %{sender} te kontaktet tuaj, që këto postime të mos përfundojnë te dosja juaj e Të padëshiruarve.
|
||||||
title: U regjistruat
|
title: U regjistruat
|
||||||
unsubscribe: Shpajtohuni
|
unsubscribe: Shpajtohuni
|
||||||
|
disabled: E çaktivizuar
|
||||||
inactive: Joaktiv
|
inactive: Joaktiv
|
||||||
|
no_access: S’lejohet përdorim
|
||||||
status: Gjendje
|
status: Gjendje
|
||||||
subscribers: Pajtimtarë
|
subscribers: Pajtimtarë
|
||||||
errors:
|
errors:
|
||||||
|
|||||||
@ -495,6 +495,21 @@ vi:
|
|||||||
no_lists_yet: Chưa có danh sách nào
|
no_lists_yet: Chưa có danh sách nào
|
||||||
last_email: Email gần nhất
|
last_email: Email gần nhất
|
||||||
lead: Các tài khoản đã kích hoạt tính năng này và có người đăng ký sẽ hiển thị bên dưới.
|
lead: Các tài khoản đã kích hoạt tính năng này và có người đăng ký sẽ hiển thị bên dưới.
|
||||||
|
show:
|
||||||
|
confirm_disable_feature: Tắt bản tin email cho %{name}? Bản tin cập nhật qua email sẽ không còn được gửi cho tài khoản này nữa. Người dùng vẫn có thể bật lại tính năng này trong cài đặt tài khoản của họ. Để xóa vĩnh viễn quyền truy cập vào tính năng này, hãy chỉnh sửa quyền của tài khoản trong mục Vai trò.
|
||||||
|
confirm_remove_subscriber: "%{email} sẽ không còn nhận được email từ %{name} nữa. Hành động này không thể hoàn tác."
|
||||||
|
consent: Người đăng ký chỉ đồng ý nhận bài viết qua email. Vui lòng không sử dụng danh sách này cho mục đích khác.
|
||||||
|
date: Ngày đăng ký
|
||||||
|
disable_feature: Tắt tính năng
|
||||||
|
disabled: Tính năng này đã bị vô hiệu hóa và email không còn được gửi đến danh sách này nữa.
|
||||||
|
email: Địa chỉ email
|
||||||
|
empty:
|
||||||
|
hint: Hiện chưa có ai đăng ký theo dõi tài khoản này.
|
||||||
|
no_subscribers_yet: Chưa có đăng ký theo dõi
|
||||||
|
enable_feature: Bật tính năng
|
||||||
|
no_access_html: Tài khoản này không còn quyền hạn cần thiết để kích hoạt tính năng này nữa. Thay đổi trong <a href="%{roles_path}">Vai trò</a>.
|
||||||
|
title: Bản tin email của %{name}
|
||||||
|
view_account: Xem tài khoản
|
||||||
status: Trạng thái
|
status: Trạng thái
|
||||||
subscribers: Người đăng ký đọc
|
subscribers: Người đăng ký đọc
|
||||||
title: Danh sách gửi thư
|
title: Danh sách gửi thư
|
||||||
@ -1510,7 +1525,9 @@ vi:
|
|||||||
success_html: Bạn sẽ bắt đầu nhận được email khi %{name} đăng tút mới. Thêm %{sender} vào danh bạ của bạn để những tút này không bị chuyển vào thư mục Spam.
|
success_html: Bạn sẽ bắt đầu nhận được email khi %{name} đăng tút mới. Thêm %{sender} vào danh bạ của bạn để những tút này không bị chuyển vào thư mục Spam.
|
||||||
title: Bạn đã đăng ký đọc
|
title: Bạn đã đăng ký đọc
|
||||||
unsubscribe: Hủy đăng ký đọc
|
unsubscribe: Hủy đăng ký đọc
|
||||||
|
disabled: Đã tắt
|
||||||
inactive: Không hoạt động
|
inactive: Không hoạt động
|
||||||
|
no_access: Không có quyền
|
||||||
status: Trạng thái
|
status: Trạng thái
|
||||||
subscribers: Người đăng ký đọc
|
subscribers: Người đăng ký đọc
|
||||||
emoji_styles:
|
emoji_styles:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user