Merge commit '3473b8a65278783bc74ce2738aea98cca0c7a5ed' into glitch-soc/merge-upstream

Conflicts:
- `config/settings.yml`:
  Upstream added a new setting textually adjacent to ones modified by glitch-soc's.
  Added upstream's new setting.
This commit is contained in:
Claire committed 2026-04-25 09:20:39 +02:00
commit b3bae7e78f
104 files changed
+1213 -186

No files matched your search

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1440 1245"><path fill="#ffb900" d="M438.957 163.115c25.062 5.75 49.562 1.125 66.312-10.687-9.813-17.875-29.688-32.563-55.063-38.312-19.875-4.625-39.5-2.625-55.063 4.312 1.75-1.125 3.438-2.312 5.188-3.75 18.437-11.812 37.5-23.062 57.375-32.874-17.875 8.625-35.438 18.437-52.438 28.812 11.5-10.688 20.75-25.938 25.063-44.126 6.062-25.062 1.75-49.562-9.813-66.312-17.874 9.5-33.125 29.375-39.188 54.5-6.062 24.5-2 48.75 9.25 65.438-25.937 17-50.749 35.749-74.062 56.188 11.25-13.812 18.749-32.875 19.625-54.5.875-26.5-8.625-50.438-23.937-65.126-16.126 13.563-27.375 36.626-28.251 63.126-.875 26.25 8.375 49.874 23.063 64.312-23.626 21.625-45.812 45-66 69.749 7.5-14.437 11.25-32.875 9.25-52.188-2.625-26.812-15.251-49.563-32.563-62.251-14.437 15.563-22.5 40.375-19.875 67.438 2.625 26.5 15 48.749 31.688 61.374a726 726 0 0 0-34.312 49c-6.312 10.063-12.125 20.188-17.874 30.563 4.875-15.875 5.187-34.563-.313-53.625-7.5-26.5-24.5-47.251-44.125-56.812-11.813 18.437-15.251 44.688-7.813 71.5 7.5 25.937 23.626 46.437 42.938 56.188-12.688 24.812-23.938 50.437-33.438 76.688-.562-14.126-4.312-29.126-11.5-43.251-13.25-25.375-34.562-42.688-56.5-48.125-8.062 21.062-6.312 48.437 6.938 73.812 12.125 23.374 31.437 40.062 51.625 46.688-.875 3.187-2 6.312-2.875 9.5-7.187 24.5-12.687 49.312-17 74.374-2.875-14.437-9.5-29.125-19.625-42.688-17.875-23.062-42.375-36.625-65.125-38.312-4.313 22.5 2.312 49.875 20.188 72.938 16.437 21.625 38.937 34.562 60.562 37.749-2.875 24.188-4.625 48.437-4.625 72.625v7.813c-5.5-12.375-13.812-24.188-24.812-34.563-21.625-20.188-48.437-30-71.812-27.688-.562 23.375 10.688 49.563 32.312 70.063 19.625 18.437 43.812 27.937 65.75 27.937a582 582 0 0 0 9.499 77c-7.5-10.375-17.312-19.875-29.374-27.937-25.063-16.438-53.938-21.063-76.688-14.688 3.75 23.375 19.625 47.563 44.688 64 22.5 14.688 47.874 19.875 69.188 16.437 1.75 6.625 3.187 13.25 5.187 19.875 4.625 16.437 9.813 32.874 15.875 49-8.938-8.063-19.312-15-31.437-20.438-28.563-12.374-58.5-12.374-81-2.312 7.5 23.375 27.937 45.563 56.5 57.937 25.062 10.938 51.312 12.375 72.374 5.5 8.938 20.188 19 40.063 30.251 59.375-10.063-6.313-21.937-11.5-34.563-14.437-30.25-7.188-59.937-1.75-80.125 12.125 11.812 21.621 35.749 39.811 66 47.001 26.5 6.31 52.437 2.87 71.812-7.19 12.375 18.75 25.937 36.88 40.375 54.19-9.813-3.44-20.75-5.75-32.312-6.63-31.688-2-60.563 8.63-78.126 26.25 15.563 19.63 42.688 33.75 74.375 35.75 26.812 1.75 51.312-5.75 69.188-18.43 14.688 15.25 30 29.68 46.125 43.25-10.937-1.44-22.5-1.75-34.312 0-33.749 4.31-61.688 21.31-76.688 43.5 20.438 17.56 51.875 26.5 85.626 22.19 27.937-3.75 51.874-15.88 67.749-32.57 27.375 19.88 56.5 37.5 86.749 52.75 5.5 2.63 11.813.31 14.438-4.87 2.625-5.5.312-11.82-4.875-14.44-23.938-11.5-47-24.81-69.188-39.5 14.688-20.19 21.937-49 17.874-79.87-4.312-33.75-21.312-61.69-43.5-76.69-17.562 20.43-26.5 51.87-22.188 85.62 2.625 19 8.938 36.31 18.188 50.19-21.312-15.87-41.812-32.87-61.125-51.31 17-16.13 28.563-41.25 30.251-70.06 1.75-31.69-8.938-60.255-26.5-78.129-19.626 15.562-33.438 42.939-35.188 74.689-1.125 18.19 2 35.19 8.062 49.87-18.188-19.31-35.437-39.81-51-61.37 18.75-12.69 34.312-34.564 40.626-62.001 7.187-30.251 2-59.938-12.126-80.126-21.625 11.5-39.812 35.438-47 65.75-4.062 16.437-4.062 32.874-1.125 47.562-1.125-2-2.625-3.75-3.75-5.75-12.125-19-23.062-38.625-32.874-58.812 20.749-9.25 40.062-28.25 51-54.5 12.375-28.562 12.125-58.5 1.75-81-23.375 7.813-45.251 28.251-57.626 56.812-6.937 15.875-9.812 32.312-9.25 47.563-9.812-23.063-18.437-46.437-25.374-70.312-.313-.563-.313-1.125-.313-1.438 21.312-5.5 42.375-21.062 57.063-44.125 16.125-25.375 20.188-54.188 13.563-77-23.375 4.063-47.251 20.188-63.438 45.563-8.625 13.812-13.812 28.812-15.874 42.937-6.313-26.812-10.938-53.937-13.563-81.312 21.937-1.125 45.812-12.125 64.563-32.312 20.188-21.625 30-48.437 27.688-71.812-23.375-.562-49.563 10.688-70.063 32.312-12.125 13-20.437 27.937-24.812 42.688-.563-9.812-.875-19.625-1.125-29.688-.313-19.312.562-38.625 2.312-57.937 21.312 2.875 46.438-3.75 68.063-19.875 23.375-17.312 37.188-41.812 39.188-64.563-22.5-4.625-49.875 1.75-73.251 19-14.125 10.375-24.5 23.375-31.125 36.875 3.438-27.688 8.938-55.375 16.125-82.437 20.438 6.625 46.438 4.312 70.312-8.063 25.375-13.25 42.688-34.562 48.126-56.5-21.063-8.062-48.438-6.312-73.812 6.938-16.438 8.625-29.375 20.437-38.063 33.749 9.25-29.688 20.437-58.812 33.749-86.749 18.438 10.062 43.5 12.687 68.626 5.187 26.5-7.812 46.688-25.062 55.937-45-18.437-11.5-45-14.688-71.5-6.937-17.312 5.187-32 14.437-42.688 25.688 6.625-12.375 13.563-24.812 20.749-36.875 7.813-12.687 15.875-25.063 24.5-37.188 16.126 12.375 40.063 18.749 65.75 15.251 26.812-3.75 49-17.312 60.812-35.188-16.126-13.812-41.251-21.063-68.063-17.312-17.312 2.312-32.312 8.937-44.375 17.874 17.875-23.374 37.5-45.812 58.5-66.562 13.563 15.562 36.312 25.937 62.251 26.812 26.5.875 50.437-8.938 64.875-24.188-13.563-16.126-36.626-27.126-63.438-27.938-19.625-.562-37.5 4.625-51.625 13.25 23.375Line truncated

After

Width:  |  Height:  |  Size: 9.5 KiB

+2 -1
View File
@@ -153,10 +153,11 @@ export function resetCompose() {
};
}
export const focusCompose = (defaultText = '') => (dispatch, getState) => {
export const focusCompose = (defaultText = '', caretStart = false) => (dispatch, getState) => {
dispatch({
type: COMPOSE_FOCUS,
defaultText,
caretStart,
});
ensureComposeIsVisible(getState);
@@ -11,7 +11,8 @@ export interface ApiCollectionJSON {
account_id: string;
id: string;
uri: string | null;
uri: string;
url: string;
local: boolean;
item_count: number;
@@ -56,7 +57,7 @@ export interface CollectionAccountItem {
id: string;
account_id?: string; // Only present when state is 'accepted' (or the collection is your own)
state: 'pending' | 'accepted' | 'rejected' | 'revoked';
position: number;
created_at: string;
}
export interface WrappedCollectionAccountItem {
@@ -2,6 +2,8 @@ import { useState, useCallback, useRef, useId } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import type {
OffsetValue,
UsePopperOptions,
@@ -18,9 +20,10 @@ import classes from './styles.module.scss';
const offset = [0, 4] as OffsetValue;
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
export const AltTextBadge: React.FC<{ description: string }> = ({
description,
}) => {
export const AltTextBadge: React.FC<{
description: string;
className?: string;
}> = ({ description, className }) => {
const intl = useIntl();
const uniqueId = useId();
const popoverId = `${uniqueId}-popover`;
@@ -48,7 +51,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
<button
type='button'
ref={buttonRef}
className='media-gallery__alt__label'
className={classNames('media-gallery__alt__label', className)}
onClick={handleClick}
aria-expanded={open}
aria-controls={popoverId}
@@ -24,9 +24,14 @@ export const NumberFieldsItem: React.FC<ItemProps> = ({
link,
children,
className,
...restProps
}) => {
return (
<li className={classNames(classes.item, className)} title={hint}>
<li
{...restProps}
className={classNames(classes.item, className)}
title={hint}
>
{label}
{link ? (
<NavLink exact to={link}>
@@ -14,6 +14,7 @@
}
a,
button,
strong {
display: block;
font-weight: 600;
@@ -25,10 +26,21 @@
a {
padding: 0;
text-decoration: none;
}
a,
button {
&:hover,
&:focus {
text-decoration: underline;
}
}
button {
appearance: none;
background: none;
border: none;
display: block;
padding: 0;
}
}
@@ -3,7 +3,7 @@ import { PureComponent } from 'react';
import { FormattedMessage, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
@@ -18,6 +18,7 @@ import { injectIntl } from './intl';
const messages = defineMessages({
aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
aboutThisServer: { id: 'server_banner.more_about_this_server', defaultMessage: 'More about this server'},
});
const mapStateToProps = state => ({
@@ -47,9 +48,14 @@ class ServerBanner extends PureComponent {
<FormattedMessage id='server_banner.is_one_of_many' defaultMessage='{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank' rel='noopener'>Mastodon</a> }} />
</div>
<Link to='/about'>
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
</Link>
<NavLink to='/about'>
<ServerHeroImage
blurhash={server.getIn(['thumbnail', 'blurhash'])}
src={server.getIn(['thumbnail', 'url'])}
alt={intl.formatMessage(messages.aboutThisServer)}
className='server-banner__hero'
/>
</NavLink>
<div className='server-banner__description'>
{isLoading ? (
@@ -2,9 +2,14 @@ import { useCallback, useState } from 'react';
import classNames from 'classnames';
import { Blurhash } from './blurhash';
import { AltTextBadge } from '../alt_text_badge';
import { Blurhash } from '../blurhash';
import classes from './styles.module.scss';
interface Props {
withAltBadge?: boolean;
alt: string;
src: string;
srcSet?: string;
blurhash?: string;
@@ -12,9 +17,11 @@ interface Props {
}
export const ServerHeroImage: React.FC<Props> = ({
alt,
src,
srcSet,
blurhash,
withAltBadge,
className,
}) => {
const [loaded, setLoaded] = useState(false);
@@ -24,12 +31,12 @@ export const ServerHeroImage: React.FC<Props> = ({
}, [setLoaded]);
return (
<div
className={classNames('image', { loaded }, className)}
role='presentation'
>
<div className={classNames('image', { loaded }, className)}>
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
<img src={src} srcSet={srcSet} alt='' onLoad={handleLoad} />
<img src={src} srcSet={srcSet} alt={alt} onLoad={handleLoad} />
{withAltBadge && alt && (
<AltTextBadge description={alt} className={classes.altBadge} />
)}
</div>
);
};
@@ -0,0 +1,5 @@
.altBadge {
position: absolute;
bottom: 8px;
inset-inline-end: 8px;
}
+25 -7
View File
@@ -31,6 +31,7 @@ import { getHashtagBarForStatus } from './hashtag_bar';
import StatusActionBar from './status_action_bar';
import StatusContent from './status_content';
import { StatusThreadLabel } from './status_thread_label';
import { CollectionPreviewCard } from '../features/collections/components/collection_preview_card';
const domParser = new DOMParser();
@@ -547,13 +548,30 @@ class Status extends ImmutablePureComponent {
);
}
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
key={`${status.get('id')}-${status.get('edited_at')}`}
card={status.get('card')}
sensitive={status.get('sensitive')}
/>
);
const cardUrl = status.getIn(['card', 'url']);
const taggedCollection = (
status.get('tagged_collections')
).find((item) => compareUrls(item.get('url'), cardUrl));
if (taggedCollection) {
media = <CollectionPreviewCard collection={taggedCollection} />;
} else {
media = (
<Card
key={`${status.get('id')}-${status.get('edited_at')}`}
card={status.get('card')}
sensitive={status.get('sensitive')}
/>
);
}
} else if (status.get('tagged_collections').size) {
const firstLinkedCollection = status.get('tagged_collections').first();
if (firstLinkedCollection) {
media = (
<CollectionPreviewCard collection={firstLinkedCollection.toJS()} />
);
}
}
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
@@ -4,7 +4,9 @@ import type { ComponentProps, FC } from 'react';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { ApiCollectionJSON } from '@/mastodon/api_types/collections';
import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
import { getCollectionPath } from '@/mastodon/features/collections/utils';
import type { OnElementHandler } from '@/mastodon/utils/html';
export interface HandledLinkProps {
@@ -13,6 +15,7 @@ export interface HandledLinkProps {
prevText?: string;
hashtagAccountId?: string;
mention?: Pick<ApiMentionJSON, 'id' | 'acct'>;
collection?: Pick<ApiCollectionJSON, 'id'>;
}
export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
@@ -21,6 +24,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
prevText,
hashtagAccountId,
mention,
collection,
className,
children,
...props
@@ -57,6 +61,15 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
{children}
</Link>
);
} else if (collection) {
return (
<Link
className={classNames(className)}
to={getCollectionPath(collection.id)}
>
{children}
</Link>
);
}
// Non-absolute paths treated as internal links. This shouldn't happen, but just in case.
@@ -18,6 +18,7 @@ import { languages as preloadedLanguages } from 'mastodon/initial_state';
import { EmojiHTML } from './emoji/html';
import { injectIntl } from './intl';
import { HandledLink } from './status/handled_link';
import { compareUrls } from '../utils/compare_urls';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
@@ -71,17 +72,6 @@ const mapStateToProps = state => ({
languages: state.getIn(['server', 'translationLanguages', 'items']),
});
const compareUrls = (href1, href2) => {
try {
const url1 = new URL(href1);
const url2 = new URL(href2);
return url1.origin === url2.origin && url1.pathname === url2.pathname && url1.search === url2.search;
} catch {
return false;
}
};
class StatusContent extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
@@ -166,7 +156,13 @@ class StatusContent extends PureComponent {
handleElement = (element, { key, ...props }, children) => {
if (element instanceof HTMLAnchorElement) {
const mention = this.props.status.get('mentions').find(item => compareUrls(element.href, item.get('url')));
const mention = this.props.status.get('mentions').find(
item => compareUrls(element.href, item.get('url'))
);
const taggedCollection = this.props.status.get('tagged_collections').find(
item => compareUrls(element.href, item.get('url'))
)
return (
<HandledLink
{...props}
@@ -174,6 +170,7 @@ class StatusContent extends PureComponent {
text={element.innerText}
hashtagAccountId={this.props.status.getIn(['account', 'id'])}
mention={mention?.toJSON()}
collection={taggedCollection?.toJSON()}
key={key}
>
{children}
@@ -82,7 +82,14 @@ class About extends PureComponent {
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
<div className='scrollable about'>
<div className='about__header'>
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
<ServerHeroImage
withAltBadge
alt={server.getIn(['thumbnail', 'description']) ?? ''}
blurhash={server.getIn(['thumbnail', 'blurhash'])}
src={server.getIn(['thumbnail', 'url'])}
srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')}
className='about__header__hero'
/>
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
<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>
@@ -23,7 +23,7 @@ export const AccountEditEmptyColumn: FC<{
}
return (
<Column bindToDocument={!multiColumn} className={classes.column}>
<Column bindToDocument={!multiColumn}>
<LoadingIndicator />
</Column>
);
@@ -38,7 +38,7 @@ export const AccountEditColumn: FC<{
return (
<>
<Column bindToDocument={!multiColumn} className={classes.column}>
<Column bindToDocument={!multiColumn}>
<ColumnHeader
title={title}
className={classes.columnHeader}
@@ -53,7 +53,7 @@ export const AccountEditColumn: FC<{
}
/>
{children}
<div className='scrollable'>{children}</div>
</Column>
<Helmet>
<title>{title}</title>
@@ -17,6 +17,7 @@ import { ToggleField } from '@/mastodon/components/form_fields';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import { autoPlayGif } from '@/mastodon/initial_state';
import {
fetchProfile,
@@ -175,7 +176,7 @@ export const AccountEdit: FC = () => {
}, [dispatch, profile?.bot]);
// Normally we would use the account emoji, but we want all custom emojis to be available to render after editing.
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
const htmlHandlers = useElementHandledLink({
hashtagAccountId: profile?.id,
});
@@ -1,4 +1,10 @@
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
import {
forwardRef,
useCallback,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import type { FC, FocusEventHandler } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@@ -9,6 +15,7 @@ import { closeModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button';
import type { FieldStatus } from '@/mastodon/components/form_fields';
import { EmojiTextInputField } from '@/mastodon/components/form_fields';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import {
removeField,
selectFieldById,
@@ -104,11 +111,6 @@ const selectFieldLimits = createAppSelector(
const RECOMMENDED_LIMIT = 40;
const selectEmojiCodes = createAppSelector(
[(state) => state.custom_emojis],
(emojis) => emojis.map((emoji) => emoji.get('shortcode')).toArray(),
);
interface ConfirmationMessage {
message: string;
confirm: string;
@@ -143,7 +145,11 @@ export const EditFieldModal = forwardRef<
value?: FieldStatus;
}>({});
const customEmojiCodes = useAppSelector(selectEmojiCodes);
const customEmojis = useCustomEmojis();
const customEmojiCodes = useMemo(
() => Object.keys(customEmojis ?? {}),
[customEmojis],
);
const checkField = useCallback(
(value: string): FieldStatus | null => {
if (!value.trim()) {
@@ -35,6 +35,7 @@ import { CSS } from '@dnd-kit/utilities';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { normalizeKey } from '@/mastodon/components/hotkeys/utils';
import { Icon } from '@/mastodon/components/icon';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import type { FieldData } from '@/mastodon/reducers/slices/profile_edit';
import {
patchProfile,
@@ -217,7 +218,7 @@ export const ReorderFieldsModal: FC<DialogModalProps> = ({ onClose }) => {
void dispatch(patchProfile({ fields_attributes: newFields })).then(onClose);
}, [dispatch, fieldKeys, fields, onClose]);
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
return (
// Add a wrapper here in the capture phase, so that it can be intercepted before the window listener in ModalRoot.
@@ -126,11 +126,6 @@
// Column component
.column {
border: 1px solid var(--color-border-primary);
border-top-width: 0;
}
.columnHeader {
:global(.column-header__buttons) {
align-items: center;
@@ -258,6 +253,10 @@
padding: 24px;
border-bottom: 1px solid var(--color-border-primary);
font-size: 15px;
&:last-child {
border-bottom: none;
}
}
.sectionHeader {
@@ -25,12 +25,9 @@ import Column from 'mastodon/features/ui/components/column';
import { useAccount } from 'mastodon/hooks/useAccount';
import { useAccountId } from 'mastodon/hooks/useAccountId';
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
import {
fetchAccountCollections,
selectAccountCollections,
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { useAccountCollections } from '../collections';
import { CollectionListItem } from '../collections/components/collection_list_item';
import { areCollectionsEnabled } from '../collections/utils';
@@ -59,10 +56,6 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
useEffect(() => {
if (accountId) {
void dispatch(fetchEndorsedAccounts({ accountId }));
if (collectionsEnabled) {
void dispatch(fetchAccountCollections({ accountId }));
}
}
}, [accountId, dispatch]);
@@ -73,9 +66,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
ImmutableList(),
) as ImmutableList<string>,
);
const { collections, status: collectionsLoadStatus } = useAppSelector(
(state) => selectAccountCollections(state, accountId ?? null),
);
const { collections, status: collectionsLoadStatus } =
useAccountCollections(accountId);
const { listedCollections = [], unlistedCollections = [] } = Object.groupBy(
collections,
@@ -1,8 +1,9 @@
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { openModal } from '@/mastodon/actions/modal';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import {
NumberFields,
@@ -10,6 +11,7 @@ import {
} from '@/mastodon/components/number_fields';
import { ShortNumber } from '@/mastodon/components/short_number';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useAppDispatch } from '@/mastodon/store';
export const AccountNumberFields: FC<{ accountId: string }> = ({
accountId,
@@ -21,6 +23,13 @@ export const AccountNumberFields: FC<{ accountId: string }> = ({
[account?.created_at],
);
const dispatch = useAppDispatch();
const showJoinModal = useCallback(() => {
dispatch(
openModal({ modalType: 'ACCOUNT_JOIN_DATE', modalProps: { accountId } }),
);
}, [accountId, dispatch]);
if (!account) {
return null;
}
@@ -60,15 +69,17 @@ export const AccountNumberFields: FC<{ accountId: string }> = ({
}
hint={intl.formatDate(account.created_at)}
>
{createdThisYear ? (
<FormattedDateWrapper
value={account.created_at}
month='short'
day='2-digit'
/>
) : (
<FormattedDateWrapper value={account.created_at} year='numeric' />
)}
<button type='button' onClick={showJoinModal}>
{createdThisYear ? (
<FormattedDateWrapper
value={account.created_at}
month='short'
day='2-digit'
/>
) : (
<FormattedDateWrapper value={account.created_at} year='numeric' />
)}
</button>
</NumberFieldsItem>
</NumberFields>
);
@@ -13,7 +13,7 @@ import {
import type { AccountField } from '../common';
import { useFieldHtml } from '../hooks/useFieldHtml';
import classes from './styles.module.css';
import classes from './styles.module.scss';
export const AccountFieldModal: FC<{
onClose: () => void;
@@ -0,0 +1,270 @@
import { useCallback, useMemo } from 'react';
import type { FC } from 'react';
import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
import AnniversaryImage from '@/images/anniversary.svg?react';
import { focusCompose, resetCompose } from '@/mastodon/actions/compose';
import { closeModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button';
import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import { IconButton } from '@/mastodon/components/icon_button';
import { ModalShell, ModalShellBody } from '@/mastodon/components/modal_shell';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import {
createAppSelector,
useAppDispatch,
useAppSelector,
} from '@/mastodon/store';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import classes from './styles.module.scss';
const closeMessage = defineMessage({
id: 'lightbox.close',
defaultMessage: 'Close',
});
const selectServerName = createAppSelector(
[
(state) => state.accounts,
(_, accountId: string) => accountId,
(state) => state.server.getIn(['server', 'domain']) as string | undefined,
],
(accounts, accountId, serverDomain) => {
const acct = accounts.getIn([accountId, 'acct']) as string | undefined;
if (!acct) {
return undefined;
}
const domain = acct.split('@').at(1);
if (domain) {
return domain;
}
return serverDomain;
},
);
export const AccountJoinModal: FC<{
accountId: string;
onClose: () => void;
}> = ({ accountId, onClose }) => {
const intl = useIntl();
const account = useAccount(accountId);
const currentId = useCurrentAccountId();
const isMe = accountId === currentId;
const createdAtStr = account?.created_at;
const anniversary = useMemo(() => {
if (!createdAtStr) {
return null;
}
const now = new Date();
const createdAt = new Date(createdAtStr);
if (
now.getMonth() === createdAt.getMonth() &&
now.getDate() === createdAt.getDate()
) {
return now.getFullYear() - createdAt.getFullYear();
}
return null;
}, [createdAtStr]);
const domain = useAppSelector((state) => selectServerName(state, accountId));
const dispatch = useAppDispatch();
const handle = account?.acct;
const handleShare = useCallback(() => {
if (anniversary === null) {
return;
}
let shareText = '#Fediversary';
if (anniversary === 0) {
shareText = isMe ? '#firstday' : '#welcome';
}
if (!isMe && handle) {
shareText = `@${handle} ${shareText}`;
}
dispatch(resetCompose());
dispatch(focusCompose(`\n\n${shareText}`, true));
dispatch(closeModal({ modalType: 'ACCOUNT_JOIN_DATE', ignoreFocus: true }));
}, [anniversary, handle, dispatch, isMe]);
return (
<ModalShell className={classes.joinShell}>
<ModalShellBody className={classes.joinWrapper}>
<AccountAnniversaryImage anniversary={anniversary} />
<div>
<AccountJoinMessage
name={<DisplayNameSimple account={account} />}
isMe={isMe}
serverName={domain}
anniversary={anniversary}
/>
<h1>
<FormattedDateWrapper
value={account?.created_at}
month='short'
day='numeric'
year='numeric'
/>
</h1>
</div>
<AccountAnniversaryShare
anniversary={anniversary}
onShare={handleShare}
isMe={isMe}
/>
<IconButton
iconComponent={CloseIcon}
icon='times'
onClick={onClose}
title={intl.formatMessage(closeMessage)}
className={classes.joinClose}
/>
</ModalShellBody>
</ModalShell>
);
};
const AccountJoinMessage: FC<{
name: React.JSX.Element;
isMe: boolean;
serverName?: string;
anniversary: number | null;
}> = ({ name, isMe, serverName, anniversary }) => {
if (anniversary === 0) {
if (isMe) {
return (
<FormattedMessage
id='account.join_modal.me_today'
defaultMessage='It’s your first day on {server}!'
tagName='p'
values={{
server: serverName,
}}
/>
);
}
return (
<FormattedMessage
id='account.join_modal.other_today'
defaultMessage='It’s {name}’s first day on {server}!'
tagName='p'
values={{
name,
server: serverName,
}}
/>
);
}
if (isMe) {
if (anniversary !== null && anniversary > 0) {
return (
<FormattedMessage
id='account.join_modal.me_anniversary'
defaultMessage='Happy Fediversary! You joined {server} on'
tagName='p'
values={{
server: serverName,
}}
/>
);
}
return (
<FormattedMessage
id='account.join_modal.me'
defaultMessage='You joined {server} on'
tagName='p'
values={{
server: serverName,
}}
/>
);
}
return (
<FormattedMessage
id='account.join_modal.other'
defaultMessage='{name} joined {server} on'
tagName='p'
values={{
name,
server: serverName,
}}
/>
);
};
const AccountAnniversaryImage: FC<{ anniversary: number | null }> = ({
anniversary,
}) => {
if (anniversary === null) {
return null;
}
return (
<div className={classes.joinBanner}>
<AnniversaryImage role='presentation' />
<h2>{anniversary || 1}</h2>
{anniversary === 0 && (
<FormattedMessage
id='account.join_modal.day'
defaultMessage='Day'
tagName='h3'
/>
)}
{anniversary > 0 && (
<FormattedMessage
id='account.join_modal.years'
defaultMessage='{number, plural, one {year} other {years}}'
values={{ number: anniversary }}
tagName='h3'
/>
)}
</div>
);
};
const AccountAnniversaryShare: FC<{
anniversary: number | null;
onShare: () => void;
isMe: boolean;
}> = ({ anniversary, onShare, isMe }) => {
if (anniversary === null) {
return null;
}
return (
<Button onClick={onShare}>
{anniversary === 0 && isMe && (
<FormattedMessage
id='account.join_modal.share.intro'
defaultMessage='Share an intro post'
/>
)}
{anniversary === 0 && !isMe && (
<FormattedMessage
id='account.join_modal.share.welcome'
defaultMessage='Share a welcome post'
/>
)}
{anniversary > 0 && (
<FormattedMessage
id='account.join_modal.share.celebrate'
defaultMessage='Share a celebratory post'
/>
)}
</Button>
);
};
@@ -13,7 +13,7 @@ import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { ConfirmationModal } from '../../ui/components/confirmation_modals';
import classes from './styles.module.css';
import classes from './styles.module.scss';
const messages = defineMessages({
newTitle: {
@@ -1,32 +0,0 @@
.noteCallout {
margin-bottom: 16px;
}
.noteInput {
min-height: 70px;
width: 100%;
padding: 8px;
border-radius: 8px;
box-sizing: border-box;
background: var(--color-bg-primary);
border: 1px solid var(--color-border-primary);
appearance: none;
resize: none;
margin-top: 4px;
}
.noteInput:focus-visible {
outline: var(--outline-focus-default);
outline-offset: 2px;
}
.fieldName,
.fieldValue {
word-break: break-all;
}
.fieldValue {
color: var(--color-text-primary);
font-weight: 600;
margin-top: 4px;
}
@@ -0,0 +1,108 @@
@use '@/styles/mastodon/variables' as *;
.noteCallout {
margin-bottom: 16px;
}
.noteInput {
min-height: 70px;
width: 100%;
padding: 8px;
border-radius: 8px;
box-sizing: border-box;
background: var(--color-bg-primary);
border: 1px solid var(--color-border-primary);
appearance: none;
resize: none;
margin-top: 4px;
}
.noteInput:focus-visible {
outline: var(--outline-focus-default);
outline-offset: 2px;
}
.fieldName,
.fieldValue {
word-break: break-all;
}
.fieldValue {
color: var(--color-text-primary);
font-weight: 600;
margin-top: 4px;
}
@media screen and (min-width: ($mobile-breakpoint + 1)) {
.joinShell {
> :global(.safety-action-modal__top) {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
border-bottom-width: 1px;
}
}
}
.joinWrapper {
display: flex;
flex-direction: column;
color: var(--color-text-primary);
align-items: center;
justify-content: center;
position: relative;
min-height: 120px;
gap: 16px;
p,
h1 {
text-align: center;
}
p {
font-size: 13px;
}
h1 {
margin-top: 8px;
margin-bottom: 0;
font-size: 17px;
font-weight: 600;
}
}
.joinBanner {
width: 120px;
height: 110px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
svg {
width: 100%;
position: absolute;
top: 0;
left: 0;
}
h2 {
font-size: 40px;
font-weight: 600;
line-height: 40px;
margin-top: 8px;
}
h3 {
font-size: 13px;
color: var(--color-text-secondary);
text-transform: uppercase;
}
}
.joinClose {
position: absolute;
top: 0;
right: 0;
z-index: 1;
}
@@ -13,6 +13,8 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { useAccount } from 'mastodon/hooks/useAccount';
import { domain } from 'mastodon/initial_state';
import { getCollectionPath } from '../utils';
import classes from './collection_lockup.module.scss';
export const AvatarGrid: React.FC<{
@@ -27,10 +29,10 @@ export const AvatarGrid: React.FC<{
sensitive ? classes.avatarGridSensitive : null,
)}
>
{avatarIds.map((id) => (
{avatarIds.map((id, index) => (
<AvatarById
animate={false}
key={id}
key={index}
accountId={id}
className={classes.avatar}
size={25}
@@ -67,7 +69,7 @@ export const CollectionLockup: React.FC<CollectionLockupProps> = ({
/>
<div>
<h2 id={linkId}>
<Link to={`/collections/${id}`} className={classes.link}>
<Link to={getCollectionPath(id)} className={classes.link}>
{name}
</Link>
</h2>
@@ -15,6 +15,7 @@ import type { MenuItem } from 'mastodon/models/dropdown_menu';
import { useAppDispatch } from 'mastodon/store';
import { messages as editorMessages } from '../editor';
import { getCollectionPath } from '../utils';
const messages = defineMessages({
view: {
@@ -120,7 +121,7 @@ export const CollectionMenu: React.FC<{
const menu = useMemo(() => {
const viewCollectionItem: MenuItem = {
text: intl.formatMessage(messages.view),
to: `/collections/${id}`,
to: getCollectionPath(id),
};
const shareItems: MenuItem[] = [
{
@@ -130,7 +131,7 @@ export const CollectionMenu: React.FC<{
{
text: intl.formatMessage(messages.copyLink),
action: () => {
void navigator.clipboard.writeText(`/collections/${id}`);
void navigator.clipboard.writeText(getCollectionPath(id));
dispatch(showAlert({ message: messages.copyLinkConfirmation }));
},
},
@@ -1,3 +1,5 @@
import classNames from 'classnames';
import type { CollectionLockupProps } from 'mastodon/features/collections/components/collection_lockup';
import { CollectionLockup } from 'mastodon/features/collections/components/collection_lockup';
@@ -13,7 +15,7 @@ export const CollectionPreviewCard: React.FC<CollectionPreviewCardProps> = ({
...otherProps
}) => {
return (
<div className={classes.wrapper}>
<div className={classNames(classes.wrapper, 'collection-preview')}>
<CollectionLockup collection={collection} {...otherProps} />
</div>
);
@@ -7,6 +7,8 @@ import { useHistory } from 'react-router-dom';
import { isFulfilled } from '@reduxjs/toolkit';
import { ComboboxMenuItem } from '@/mastodon/components/form_fields/combobox_field';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { languages } from '@/mastodon/initial_state';
import {
hasSpecialCharacters,
@@ -36,6 +38,8 @@ import {
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { getCollectionPath } from '../utils';
import classes from './styles.module.scss';
import { WizardStepTitle } from './wizard_step_title';
@@ -93,6 +97,9 @@ export const CollectionDetails: React.FC = () => {
[dispatch],
);
const accountId = useCurrentAccountId();
const { acct: currentUserName } = useAccount(accountId) ?? {};
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
@@ -128,8 +135,8 @@ export const CollectionDetails: React.FC = () => {
}),
).then((result) => {
if (isFulfilled(result)) {
history.replace(`/collections`);
history.push(`/collections/${result.payload.collection.id}`, {
history.replace(`/@${currentUserName}/collections`);
history.push(getCollectionPath(result.payload.collection.id), {
newCollection: true,
});
}
@@ -146,6 +153,7 @@ export const CollectionDetails: React.FC = () => {
dispatch,
history,
accountIds,
currentUserName,
],
);
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
import {
@@ -12,6 +12,9 @@ import {
useLocation,
} from 'react-router-dom';
import { Callout } from '@/mastodon/components/callout';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { initialState } from '@/mastodon/initial_state';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import { Column } from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
@@ -22,8 +25,11 @@ import {
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { useAccountCollections } from '..';
import { CollectionAccounts } from './accounts';
import { CollectionDetails } from './details';
import classes from './styles.module.scss';
export const messages = defineMessages({
create: {
@@ -61,11 +67,14 @@ function usePageTitle(id: string | null) {
}
}
export const userCollectionLimit = initialState?.role?.collection_limit ?? 0;
export const CollectionEditorPage: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const accountId = useCurrentAccountId();
const { id = null } = useParams<{ id?: string }>();
const { path } = useRouteMatch();
const collection = useAppSelector((state) =>
@@ -73,7 +82,18 @@ export const CollectionEditorPage: React.FC<{
);
const editorStateId = useAppSelector((state) => state.collections.editor.id);
const isEditMode = !!id;
const isLoading = isEditMode && !collection;
// When creating a new collection, we load the current account's collections
// to determine if they're allowed to create more.
const { collections: collectionList, status: collectionListStatus } =
useAccountCollections(isEditMode ? null : accountId);
const isLoading =
(isEditMode && !collection) ||
(!isEditMode && collectionListStatus === 'loading');
const canCreateMoreCollections =
isEditMode || collectionList.length < userCollectionLimit;
useEffect(() => {
if (id) {
@@ -108,7 +128,7 @@ export const CollectionEditorPage: React.FC<{
<div className='scrollable'>
{isLoading ? (
<LoadingIndicator />
) : (
) : canCreateMoreCollections ? (
<Switch>
<Route
exact
@@ -123,6 +143,8 @@ export const CollectionEditorPage: React.FC<{
render={() => <CollectionDetails />}
/>
</Switch>
) : (
<MaxCollectionsCallout />
)}
</div>
@@ -133,3 +155,21 @@ export const CollectionEditorPage: React.FC<{
</Column>
);
};
export const MaxCollectionsCallout: React.FC = () => (
<Callout
className={classes.maxCollectionsError}
title={
<FormattedMessage
id='collections.maximum_collection_count_reached'
defaultMessage='You have created the maximum number of collections'
/>
}
>
<FormattedMessage
id='collections.maximum_collection_count_description'
defaultMessage='Your server allows creation of up to {count} collections.'
values={{ count: userCollectionLimit }}
/>
</Callout>
);
@@ -77,3 +77,7 @@
.suggestionGroup {
padding-bottom: 4px;
}
.maxCollectionsError {
margin: 16px;
}
@@ -25,7 +25,12 @@ import {
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { CollectionListItem } from './components/collection_list_item';
import { messages as editorMessages } from './editor';
import {
messages as editorMessages,
MaxCollectionsCallout,
userCollectionLimit,
} from './editor';
import { areCollectionsEnabled } from './utils';
const messages = defineMessages({
headingMe: { id: 'column.my_collections', defaultMessage: 'My collections' },
@@ -35,24 +40,27 @@ const messages = defineMessages({
},
});
export function useAccountCollections(accountId: string | null | undefined) {
const dispatch = useAppDispatch();
useEffect(() => {
if (accountId && areCollectionsEnabled()) {
void dispatch(fetchAccountCollections({ accountId }));
}
}, [dispatch, accountId]);
return useAppSelector((state) => selectAccountCollections(state, accountId));
}
export const Collections: React.FC<{
multiColumn?: boolean;
}> = ({ multiColumn }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const me = useCurrentAccountId();
const accountId = useAccountId();
const account = useAccount(accountId);
const { collections, status } = useAppSelector((state) =>
selectAccountCollections(state, accountId),
);
useEffect(() => {
if (accountId) {
void dispatch(fetchAccountCollections({ accountId }));
}
}, [dispatch, accountId]);
const { collections, status } = useAccountCollections(accountId);
const emptyMessage =
status === 'error' || !accountId ? (
@@ -79,6 +87,7 @@ export const Collections: React.FC<{
</>
);
const canCreateMoreCollections = collections.length < userCollectionLimit;
const isOwnCollection = accountId === me;
const titleMessage = isOwnCollection
? messages.headingMe
@@ -99,7 +108,9 @@ export const Collections: React.FC<{
iconComponent={CollectionsFilledIcon}
multiColumn={multiColumn}
extraButton={
isOwnCollection && (
isOwnCollection &&
status === 'idle' &&
canCreateMoreCollections && (
<Link
to='/collections/new'
className='column-header__button'
@@ -113,6 +124,9 @@ export const Collections: React.FC<{
/>
<Scrollable>
{status === 'idle' && !canCreateMoreCollections && (
<MaxCollectionsCallout />
)}
<ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}>
{collections.map((item, index) => (
<CollectionListItem
@@ -3,3 +3,5 @@ import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
export function areCollectionsEnabled() {
return isServerFeatureEnabled('collections');
}
export const getCollectionPath = (id: string) => `/collections/${id}`;
@@ -267,6 +267,11 @@ export async function searchCustomEmojisByShortcodes(shortcodes: string[]) {
return results.filter((emoji) => shortcodes.includes(emoji.shortcode));
}
export async function loadAllCustomEmoji() {
const db = await loadDB();
return db.getAll('custom');
}
export async function loadLegacyShortcodesByShortcode(shortcode: string) {
const db = await loadDB();
return db.getFromIndex(
@@ -185,21 +185,16 @@ export function cleanExtraEmojis(extraEmojis?: CustomEmojiMapArg | null) {
if (!extraEmojis) {
return null;
}
if (Array.isArray(extraEmojis)) {
return extraEmojis.reduce<ExtraCustomEmojiMap>(
(acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }),
{},
);
if (!Array.isArray(extraEmojis) && !isList(extraEmojis)) {
return extraEmojis;
}
if (isList(extraEmojis)) {
return extraEmojis
.toJS()
.reduce<ExtraCustomEmojiMap>(
(acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }),
{},
);
const emojis: ExtraCustomEmojiMap = {};
const emojiArray = isList(extraEmojis) ? extraEmojis.toJS() : extraEmojis;
for (const emoji of emojiArray) {
emojis[emoji.shortcode] = emoji;
}
return extraEmojis;
return emojis;
}
/**
@@ -7,6 +7,7 @@ import elephantUIPlane from '@/images/elephant_ui_plane.svg';
import type { RenderSlideFn } from '@/mastodon/components/carousel';
import { Carousel } from '@/mastodon/components/carousel';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import { mascot } from '@/mastodon/initial_state';
import { createAppSelector, useAppSelector } from '@/mastodon/store';
@@ -23,7 +24,7 @@ const announcementSelector = createAppSelector(
export const Announcements: FC = () => {
const announcements = useAppSelector(announcementSelector);
const emojis = useAppSelector((state) => state.custom_emojis);
const emojis = useCustomEmojis();
const renderSlide: RenderSlideFn<{
id: string;
@@ -29,9 +29,12 @@ import StatusContent from 'mastodon/components/status_content';
import { QuotedStatus } from 'mastodon/components/status_quoted';
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
import { Audio } from 'mastodon/features/audio';
import { CollectionPreviewCard } from 'mastodon/features/collections/components/collection_preview_card';
import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task';
import { Video } from 'mastodon/features/video';
import { useIdentity } from 'mastodon/identity_context';
import type { CollectionAttachment } from 'mastodon/models/status';
import { compareUrls } from 'mastodon/utils/compare_urls';
import Card from './card';
@@ -260,13 +263,32 @@ export const DetailedStatus: React.FC<{
);
}
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
key={`${status.get('id')}-${status.get('edited_at')}`}
sensitive={status.get('sensitive')}
card={status.get('card')}
/>
);
const cardUrl: string = status.getIn(['card', 'url']);
const taggedCollection = status
.get('tagged_collections')
.find((item: CollectionAttachment) =>
compareUrls(item.get('url'), cardUrl),
);
if (taggedCollection) {
media = <CollectionPreviewCard collection={taggedCollection} />;
} else {
media = (
<Card
key={`${status.get('id')}-${status.get('edited_at')}`}
sensitive={status.get('sensitive')}
card={status.get('card')}
/>
);
}
} else if (status.get('tagged_collections').size) {
const firstLinkedCollection = status.get('tagged_collections').first();
if (firstLinkedCollection) {
media = (
<CollectionPreviewCard collection={firstLinkedCollection.toJS()} />
);
}
}
if (status.get('application')) {
@@ -93,6 +93,7 @@ export const MODAL_COMPONENTS = {
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })),
'ACCOUNT_JOIN_DATE': () => import('@/mastodon/features/account_timeline/modals/join_modal').then(module => ({ default: module.AccountJoinModal })),
'ACCOUNT_EDIT_NAME': accountEditModal('NameModal'),
'ACCOUNT_EDIT_BIO': accountEditModal('BioModal'),
'ACCOUNT_EDIT_PROFILE_DISPLAY': accountEditModal('ProfileDisplayModal'),
@@ -0,0 +1,35 @@
import { useEffect, useState } from 'react';
import type { ExtraCustomEmojiMap } from '../features/emoji/types';
let emojis: ExtraCustomEmojiMap | null = null;
export function useCustomEmojis() {
const [, setLoaded] = useState(emojis !== null);
useEffect(() => {
if (!emojis) {
void loadEmojisIntoCache().then(() => {
setLoaded(true);
});
}
}, []);
return emojis;
}
async function loadEmojisIntoCache() {
const { loadAllCustomEmoji } = await import('../features/emoji/database');
const emojisRaw = await loadAllCustomEmoji();
if (emojisRaw.length === 0) {
return;
}
emojis = {};
for (const emoji of emojisRaw) {
emojis[emoji.shortcode] = {
url: emoji.url,
shortcode: emoji.shortcode,
static_url: emoji.static_url,
};
}
}
+1
View File
@@ -57,6 +57,7 @@ interface Role {
permissions: string;
color: string;
highlighted: boolean;
collection_limit: number;
}
interface InitialWrapstodonState {
+99
View File
@@ -18,8 +18,12 @@
"account.add_note": "إضافة ملاحظة شخصية",
"account.add_or_remove_from_list": "الإضافة أو الإزالة من القائمة",
"account.badges.admin": "مدير",
"account.badges.blocked": "محظور",
"account.badges.bot": "آلي",
"account.badges.domain_blocked": "النطاق محظور",
"account.badges.group": "فريق",
"account.badges.muted": "مكتوم",
"account.badges.muted_until": "مكتوم إلى غاية {until}",
"account.block": "احجب @{name}",
"account.block_domain": "حظر اسم النِّطاق {domain}",
"account.block_short": "حظر",
@@ -30,6 +34,7 @@
"account.direct": "إشارة خاصة لـ @{name}",
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
"account.domain_blocking": "نطاق محظور",
"account.edit_note": "تعديل الملاحظة الشخصية",
"account.edit_profile": "تعديل الملف الشخصي",
"account.edit_profile_short": "تعديل",
"account.enable_notifications": "أشعرني عندما ينشر @{name}",
@@ -40,6 +45,8 @@
"account.featured": "معروض",
"account.featured.accounts": "ملفات شخصية",
"account.filters.all": "جميع الأنشطة",
"account.filters.boosts_toggle": "اعرض المعاد نشرها",
"account.filters.posts_boosts": "المنشورات والمعاد نشرها",
"account.filters.posts_only": "منشورات",
"account.filters.posts_replies": "المنشورات والردود",
"account.filters.replies_toggle": "اعرض الردود",
@@ -49,6 +56,7 @@
"account.follow_request": "طلب المتابعة",
"account.follow_request_cancel": "إلغاء الطلب",
"account.follow_request_cancel_short": "إلغاء",
"account.follow_request_short": "طلب المتابعة",
"account.followers": "مُتابِعون",
"account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.",
"account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}",
@@ -62,11 +70,26 @@
"account.in_memoriam": "في الذكرى.",
"account.joined_short": "انضم في",
"account.languages": "تغيير اللغات المشترَك فيها",
"account.last_active": "آخر نشاط",
"account.link_verified_on": "تمَّ التَّحقق مِن مِلْكيّة هذا الرابط بتاريخ {date}",
"account.locked_info": "تم ضبط حالة خصوصية هذا الحساب على أنه مؤمّن. إذ يراجع صاحبه يدويًا من يُسمح له بالمتابعة.",
"account.media": "وسائط",
"account.mention": "أذكُر @{name}",
"account.menu.add_to_list": "إضافة إلى القائمة…",
"account.menu.block": "حظر الحساب",
"account.menu.block_domain": "حظر {domain}",
"account.menu.copied": "تم نسخ رابط الحساب إلى الحافظة",
"account.menu.copy": "نسخ الرابط",
"account.menu.direct": "إشارة خاصة لـ",
"account.menu.mention": "إشارة",
"account.menu.mute": "كتم الحساب",
"account.menu.note.description": "مرئي لك فقط",
"account.menu.remove_follower": "إزالة المتابِع",
"account.menu.report": "الإبلاغ عن الحساب",
"account.menu.share": "مشاركة…",
"account.menu.unblock": "رفع الحظر عن الحساب",
"account.menu.unblock_domain": "رفع الحظر عن {domain}",
"account.menu.unmute": "إلغاء كتم الحساب",
"account.moved_to": "أشار {name} إلى أن حسابه الجديد الآن:",
"account.mute": "أكتم @{name}",
"account.mute_notifications_short": "كتم الإشعارات",
@@ -74,7 +97,11 @@
"account.muted": "مَكتوم",
"account.muting": "مكتوم",
"account.mutual": "أنتم تتابعون بعضكم البعض",
"account.name.copy": "نسخ المعرف",
"account.name_info": "ما معنى ذلك؟",
"account.no_bio": "لم يتم تقديم وصف.",
"account.node_modal.edit_title": "تعديل الملاحظة الشخصية",
"account.node_modal.error_unknown": "تعذر حفظ الملاحظة",
"account.node_modal.field_label": "ملاحظة شخصية",
"account.node_modal.save": "حفظ",
"account.node_modal.title": "إضافة ملاحظة شخصية",
@@ -89,6 +116,8 @@
"account.share": "شارِك الملف التعريفي لـ @{name}",
"account.show_reblogs": "اعرض إعادات نشر @{name}",
"account.statuses_counter": "{count, plural, zero {}one {{counter} مشور} two {{counter} منشور} few {{counter} منشور} many {{counter} منشور} other {{counter} منشور}}",
"account.timeline.pinned": "مثبّت",
"account.timeline.pinned.view_all": "عرض جميع المنشورات المثبتة",
"account.unblock": "إلغاء الحَظر عن @{name}",
"account.unblock_domain": "إلغاء الحَظر عن النِّطاق {domain}",
"account.unblock_domain_short": "رفع الحظر",
@@ -98,20 +127,57 @@
"account.unmute": "إلغاء الكَتم عن @{name}",
"account.unmute_notifications_short": "إلغاء كَتم الإشعارات",
"account.unmute_short": "إلغاء الكتم",
"account_edit.advanced_settings.bot_label": "حساب آلي",
"account_edit.advanced_settings.title": "الإعدادات المتقدمة",
"account_edit.bio.add_label": "إضافة سيرة ذاتية",
"account_edit.bio.edit_label": "تعديل السيرة الذاتية",
"account_edit.bio.title": "نبذة عنك",
"account_edit.bio_modal.add_title": "إضافة سيرة ذاتية",
"account_edit.bio_modal.edit_title": "تعديل السيرة الذاتية",
"account_edit.column_button": "تمّ",
"account_edit.column_title": "تعديل الملف الشخصي",
"account_edit.custom_fields.add_label": "إضافة حقل",
"account_edit.custom_fields.edit_label": "تعديل الحقل",
"account_edit.custom_fields.placeholder": "أضف ضمائر أو روابط خارجية أو أي شيء آخر ترغب في مشاركته.",
"account_edit.custom_fields.reorder_button": "إعادة ترتيب الحقول",
"account_edit.custom_fields.tip_title": "نصيحة: إضافة روابط متحقق منها",
"account_edit.custom_fields.title": "الحقول المخصصة",
"account_edit.custom_fields.verified_hint": "كيف يمكنني إضافة رابط متحقق منه؟",
"account_edit.display_name.add_label": "إضافة اسم علني",
"account_edit.display_name.edit_label": "تعديل الاسم العلني",
"account_edit.display_name.title": "الاسم العلني",
"account_edit.featured_hashtags.edit_label": "إضافة وسوم",
"account_edit.featured_hashtags.title": "الوسوم المروّجة",
"account_edit.field_actions.delete": "حذف الحقل",
"account_edit.field_actions.edit": "تعديل الحقل",
"account_edit.field_delete_modal.delete_button": "حذف",
"account_edit.field_delete_modal.title": "أتريد حذف الحقل المخصص؟",
"account_edit.field_edit_modal.add_title": "إضافة حقل مخصص",
"account_edit.field_edit_modal.discard_confirm": "تجاهل",
"account_edit.field_edit_modal.edit_title": "تعديل الحقل المخصص",
"account_edit.field_edit_modal.name_hint": "على سبيل المثال \"الموقع الشخصي\"",
"account_edit.field_edit_modal.name_label": "التسمية",
"account_edit.field_edit_modal.url_warning": "لإضافة رابط ، يرجى تضمين {protocol} في البداية.",
"account_edit.field_edit_modal.value_hint": "على سبيل المثال \"https://example.me\"",
"account_edit.field_edit_modal.value_label": "قيمة",
"account_edit.image_alt_modal.add_title": "إضافة نص بديل",
"account_edit.image_alt_modal.edit_title": "تعديل نص بديل",
"account_edit.image_alt_modal.text_label": "نص بديل",
"account_edit.image_delete_modal.delete_button": "حذف",
"account_edit.image_edit.add_button": "إضافة صورة",
"account_edit.image_edit.alt_add_button": "إضافة نص بديل",
"account_edit.image_edit.alt_edit_button": "تعديل نص بديل",
"account_edit.image_edit.remove_button": "إزالة الصورة",
"account_edit.image_edit.replace_button": "استبدال الصورة",
"account_edit.item_list.delete": "حذف {name}",
"account_edit.item_list.edit": "تعديل {name}",
"account_edit.profile_tab.button_label": "تخصيص",
"account_edit.save": "حفظ",
"account_edit.upload_modal.back": "العودة",
"account_edit.upload_modal.done": "تمّ",
"account_edit.upload_modal.next": "التالي",
"account_edit.upload_modal.step_crop.zoom": "تكبير",
"account_edit.upload_modal.step_upload.button": "تصفح الملفات",
"account_edit.upload_modal.step_upload.dragging": "إسقاط للتحميل",
"account_edit.upload_modal.step_upload.header": "اختيار صورة",
"account_edit.upload_modal.title_add.avatar": "إضافة صورة الملف الشخصي",
@@ -119,6 +185,7 @@
"account_edit.upload_modal.title_replace.avatar": "استبدال صورة الملف الشخصي",
"account_edit.upload_modal.title_replace.header": "استبدال صورة الغلاف",
"account_edit_tags.add_tag": "إضافة #{tagName}",
"account_edit_tags.suggestions": "الاقتراحات:",
"account_note.placeholder": "اضغط لإضافة مُلاحظة",
"admin.dashboard.daily_retention": "معدّل بقاء المستخدمين بعد إنشاء الحسابات، بالأيام",
"admin.dashboard.monthly_retention": "معدّل بقاء المستخدمين بعد إنشاء الحسابات، بالشهور",
@@ -145,6 +212,7 @@
"annual_report.announcement.action_dismiss": "لا شكراً",
"annual_report.nav_item.badge": "جديد",
"annual_report.shared_page.donate": "تبرع",
"annual_report.summary.archetype.oracle.name": "الحكيم",
"annual_report.summary.archetype.replier.name": "الفراشة",
"annual_report.summary.close": "اغلق",
"annual_report.summary.copy_link": "نسخ الرابط",
@@ -186,17 +254,33 @@
"closed_registrations_modal.find_another_server": "ابحث على خادم آخر",
"closed_registrations_modal.preamble": "ماستدون لامركزي، لذلك بغض النظر عن مكان إنشاء حسابك، سيكون بإمكانك المتابعة والتفاعل مع أي شخص على هذا الخادم. يمكنك حتى أن تستضيفه ذاتياً!",
"closed_registrations_modal.title": "إنشاء حساب على ماستدون",
"collection.share_modal.share_link_label": "مشاركة الرابط",
"collection.share_modal.share_via_post": "نشر على ماستدون",
"collections.block_collection_owner": "حظر الحساب",
"collections.by_account": "مِن {account_handle}",
"collections.collection_description": "الوصف",
"collections.collection_language": "اللغة",
"collections.collection_language_none": "لا شيء",
"collections.collection_name": "الاسم",
"collections.collection_topic": "الموضوع",
"collections.content_warning": "تحذير عن المحتوى",
"collections.continue": "مواصلة",
"collections.copy_link": "نسخ الرابط",
"collections.copy_link_confirmation": "نسخ الرابط إلى الحافظة",
"collections.create.basic_details_title": "المعلومات الأساسية",
"collections.create.steps": "الخطوة {step}/{total}",
"collections.detail.accounts_heading": "الحسابات",
"collections.detail.revoke_inclusion": "أزلني",
"collections.detail.sensitive_content": "محتوى حساس",
"collections.edit_details": "تعديل التفاصيل",
"collections.hints.accounts_counter": "{count}/{max} حسابات",
"collections.manage_accounts": "إدارة الحسابات",
"collections.remove_account": "إزالة",
"collections.sensitive": "حساس",
"collections.share_short": "مشاركة",
"collections.visibility_public": "للعامة",
"collections.visibility_title": "مدى الظهور",
"collections.visibility_unlisted": "غير مُدرَج",
"column.about": "عن",
"column.blocks": "المُستَخدِمون المَحظورون",
"column.bookmarks": "الفواصل المرجعية",
@@ -260,6 +344,7 @@
"confirmations.delete.message": "هل أنتَ مُتأكدٌ أنك تُريدُ حَذفَ هذا المنشور؟",
"confirmations.delete.title": "أتريد حذف المنشور؟",
"confirmations.delete_collection.confirm": "حذف",
"confirmations.delete_collection.title": "حذف \"{name}\"؟",
"confirmations.delete_list.confirm": "حذف",
"confirmations.delete_list.message": "هل أنتَ مُتأكدٌ أنكَ تُريدُ حَذفَ هذِهِ القائمة بشكلٍ دائم؟",
"confirmations.delete_list.title": "أتريد حذف القائمة؟",
@@ -283,6 +368,9 @@
"confirmations.missing_alt_text.secondary": "انشر على أي حال",
"confirmations.missing_alt_text.title": "أضف نصًا بديلًا؟",
"confirmations.mute.confirm": "أكتم",
"confirmations.private_quote_notify.cancel": "العودة إلى التحرير",
"confirmations.private_quote_notify.confirm": "نشر المنشور",
"confirmations.private_quote_notify.do_not_show_again": "لا تظهر علي هذه الرسالة مجددًا",
"confirmations.quiet_post_quote_info.dismiss": "لا تُذكرني مرة أخرى",
"confirmations.quiet_post_quote_info.got_it": "مفهوم",
"confirmations.quiet_post_quote_info.message": "عندما تقتبس منشورا هادئا للعامة، فإن منشورك سيكون أيضا مخفيا عن الخيوط الزمنية الرائجة.",
@@ -293,6 +381,7 @@
"confirmations.remove_from_followers.confirm": "إزالة المتابع",
"confirmations.remove_from_followers.message": "سيتوقف {name} عن متابعتك. هل بالتأكيد تريد المتابعة؟",
"confirmations.remove_from_followers.title": "إزالة المتابع؟",
"confirmations.revoke_collection_inclusion.confirm": "أزلني",
"confirmations.revoke_quote.confirm": "إزالة المنشور",
"confirmations.revoke_quote.message": "لا يمكن التراجع عن هذا الإجراء.",
"confirmations.revoke_quote.title": "أتريد إزالة المنشور؟",
@@ -343,6 +432,8 @@
"dropdown.empty": "حدد خيارا",
"email_subscriptions.email": "البريد الإلكتروني",
"email_subscriptions.form.action": "اشترك",
"email_subscriptions.submitted.title": "خطوة واحدة أخرى",
"email_subscriptions.validation.email.invalid": "عنوان البريد الإلكتروني غير صالح",
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
"embed.preview": "إليك ما سيبدو عليه:",
"emoji_button.activity": "الأنشطة",
@@ -437,6 +528,7 @@
"follow_suggestions.view_all": "عرض الكل",
"follow_suggestions.who_to_follow": "حسابات للمُتابَعة",
"followed_tags": "الوسوم المتابَعة",
"following.title": "يتبعه {name}",
"footer.about": "عن",
"footer.about_mastodon": "عن ماستدون",
"footer.about_server": "عن {domain}",
@@ -750,12 +842,14 @@
"notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
"onboarding.follows.back": "عودة",
"onboarding.follows.empty": "نأسف، لا يمكن عرض نتائج في الوقت الحالي. جرب البحث أو انتقل لصفحة الاستكشاف لإيجاد أشخاص للمتابعة، أو حاول مرة أخرى.",
"onboarding.follows.next": "التالي: إعداد ملفك الشخصي",
"onboarding.follows.search": "بحث",
"onboarding.follows.title": "للبدء قم بمتابعة أشخاص",
"onboarding.profile.discoverable": "اجعل ملفي الشخصي قابلاً للاكتشاف",
"onboarding.profile.discoverable_hint": "عندما تختار تفعيل إمكانية الاكتشاف على ماستدون، قد تظهر منشوراتك في نتائج البحث والمواضيع الرائجة، وقد يتم اقتراح ملفك الشخصي لأشخاص ذوي اهتمامات مماثلة معك.",
"onboarding.profile.display_name": "الاسم العلني",
"onboarding.profile.display_name_hint": "اسمك الكامل أو اسمك المرح…",
"onboarding.profile.finish": "إنهاء",
"onboarding.profile.note": "نبذة عنك",
"onboarding.profile.note_hint": "يمكنك @ذِكر أشخاص آخرين أو استعمال #الوسوم…",
"onboarding.profile.title": "إعداد الملف الشخصي",
@@ -896,6 +990,7 @@
"sign_in_banner.mastodon_is": "ماستودون هو أفضل وسيلة لمواكبة الأحداث.",
"sign_in_banner.sign_in": "تسجيل الدخول",
"sign_in_banner.sso_redirect": "تسجيل الدخول أو إنشاء حساب",
"skip_links.skip_to_content": "تخطي إلى المحتوى الرئيسي",
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
"status.admin_domain": "فتح واجهة الإشراف لـ {domain}",
"status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
@@ -905,6 +1000,10 @@
"status.cancel_reblog_private": "إلغاء إعادة النشر",
"status.cannot_quote": "غير مصرح لك باقتباس هذا المنشور",
"status.cannot_reblog": "لا يمكن إعادة نشر هذا المنشور",
"status.contains_quote": "يحتوي على اقتباس",
"status.context.loading": "تحميل المزيد من الردود",
"status.context.loading_success": "تم تحميل ردود جديدة",
"status.context.more_replies_found": "تم العثور على المزيد من الردود",
"status.context.retry": "حاول مجددًا",
"status.context.show": "إظهار",
"status.continued_thread": "تكملة للخيط",
+4
View File
@@ -71,6 +71,8 @@
"account.go_to_profile": "Перайсці да профілю",
"account.hide_reblogs": "Схаваць пашырэнні ад @{name}",
"account.in_memoriam": "У памяць.",
"account.join_modal.me": "Вы далучыліся да {server}",
"account.join_modal.years": "{number, plural, one {# год} few {# гады} many {# гадоў} other {# гады}}",
"account.joined_short": "Далучыўся",
"account.languages": "Змяніць выбраныя мовы",
"account.last_active": "Апошняя актыўнасць",
@@ -398,6 +400,8 @@
"collections.manage_accounts": "Кіраванне ўліковымі запісамі",
"collections.mark_as_sensitive": "Пазначыць як адчувальную",
"collections.mark_as_sensitive_hint": "Схаваць апісанне калекцыі і ўліковыя запісы за банерам з папярэджаннем. Назва калекцыі застанецца бачнай.",
"collections.maximum_collection_count_description": "Ваш сервер дазваляе ствараць да {count} калекцый.",
"collections.maximum_collection_count_reached": "Вы стварылі максімальную колькасць калекцый",
"collections.name_length_hint": "Максімум 40 сімвалаў",
"collections.new_collection": "Новая калекцыя",
"collections.no_collections_yet": "Пакуль няма калекцый.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Gå til profil",
"account.hide_reblogs": "Skjul fremhævelser fra @{name}",
"account.in_memoriam": "Til minde om.",
"account.join_modal.day": "Dag",
"account.join_modal.me": "Du tilmeldte dig {server}",
"account.join_modal.me_anniversary": "Tillykke med dit fedivers-jubilæum! Du tilmeldte dig {server}",
"account.join_modal.me_today": "Det er din første dag på {server}!",
"account.join_modal.other": "{name} tilmeldte sig {server}",
"account.join_modal.other_today": "Det er {name}’s første dag på {server}!",
"account.join_modal.share.celebrate": "Del et indlæg for at fejre begivenheden",
"account.join_modal.share.intro": "Del et introindlæg",
"account.join_modal.share.welcome": "Del et velkomstindlæg",
"account.join_modal.years": "{number, plural, one {år} other {år}}",
"account.joined_short": "Oprettet",
"account.languages": "Skift abonnementssprog",
"account.last_active": "Senest aktiv",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Administrer konti",
"collections.mark_as_sensitive": "Markér som sensitiv",
"collections.mark_as_sensitive_hint": "Skjuler samlingens beskrivelse og konti bag en indholdsadvarsel. Samlingens navn vil stadig være synligt.",
"collections.maximum_collection_count_description": "Din server tillader oprettelse af op til {count} samlinger.",
"collections.maximum_collection_count_reached": "Du har oprettet det maksimale antal samlinger",
"collections.name_length_hint": "Begrænset til 40 tegn",
"collections.new_collection": "Ny samling",
"collections.no_collections_yet": "Ingen samlinger endnu.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Profil aufrufen",
"account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden",
"account.in_memoriam": "Zum Andenken.",
"account.join_modal.day": "Tag",
"account.join_modal.me": "Du bist auf {server} dabei seit",
"account.join_modal.me_anniversary": "Happy Fediversary! Du bist auf {server} dabei seit",
"account.join_modal.me_today": "Dein erster Tag auf {server}!",
"account.join_modal.other": "{name} ist auf {server} dabei seit",
"account.join_modal.other_today": "{name} ist seit einem Tag auf {server}!",
"account.join_modal.share.celebrate": "Teile die Feierlichkeit",
"account.join_modal.share.intro": "Teile einen Einblick über dich",
"account.join_modal.share.welcome": "Teile ein herzliches Willkommen",
"account.join_modal.years": "{number, plural, one {Jahr} other {Jahre}}",
"account.joined_short": "Dabei seit",
"account.languages": "Sprachen verwalten",
"account.last_active": "Zuletzt aktiv",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Profile verwalten",
"collections.mark_as_sensitive": "Mit Inhaltswarnung versehen",
"collections.mark_as_sensitive_hint": "Die Beschreibung sowie enthaltenen Profile werden durch eine Inhaltswarnung ausgeblendet. Der Titel bleibt weiterhin sichtbar.",
"collections.maximum_collection_count_description": "Auf deinem Server darfst du bis zu {count} Sammlungen erstellen.",
"collections.maximum_collection_count_reached": "Du hast die Höchstzahl an Sammlungen erreicht",
"collections.name_length_hint": "Maximal 40 Zeichen",
"collections.new_collection": "Neue Sammlung",
"collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Μετάβαση στο προφίλ",
"account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}",
"account.in_memoriam": "Εις μνήμην.",
"account.join_modal.day": "Ημέρα",
"account.join_modal.me": "Έγινες μέλος στο {server} στις",
"account.join_modal.me_anniversary": "Ευτυχισμένο Fediversary! Έγινες μέλος στο {server} στις",
"account.join_modal.me_today": "Είναι η πρώτη σου μέρα στο {server}!",
"account.join_modal.other": "Ο/Η {name} έγινε μέλος στο {server} στις",
"account.join_modal.other_today": "Είναι η πρώτη μέρα του/της {name} στο {server}!",
"account.join_modal.share.celebrate": "Μοιράσου μια εορταστική ανάρτηση",
"account.join_modal.share.intro": "Μοιράσου μια εισαγωγική ανάρτηση",
"account.join_modal.share.welcome": "Μοιράσου μια ανάρτηση καλωσορίσματος",
"account.join_modal.years": "{number, plural, one {χρόνος} other {χρόνια}}",
"account.joined_short": "Έγινε μέλος",
"account.languages": "Αλλαγή εγγεγραμμένων γλωσσών",
"account.last_active": "Τελευταία ενεργός",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Διαχείριση λογαριασμών",
"collections.mark_as_sensitive": "Σήμανση ως ευαίσθητο",
"collections.mark_as_sensitive_hint": "Κρύβει την περιγραφή και τους λογαριασμούς της συλλογής πίσω από μια προειδοποίηση περιεχομένου. Το όνομα της συλλογής θα είναι ακόμη ορατό.",
"collections.maximum_collection_count_description": "Ο εξυπηρετητής σου επιτρέπει την δημιουργία μέχρι {count} συλλογών.",
"collections.maximum_collection_count_reached": "Έχεις δημιουργήσει τον μέγιστο αριθμό συλλογών",
"collections.name_length_hint": "Όριο 40 χαρακτήρων",
"collections.new_collection": "Νέα συλλογή",
"collections.no_collections_yet": "Καμία συλλογή ακόμη.",
+13
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Go to profile",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.in_memoriam": "In Memoriam.",
"account.join_modal.day": "Day",
"account.join_modal.me": "You joined {server} on",
"account.join_modal.me_anniversary": "Happy Fediversary! You joined {server} on",
"account.join_modal.me_today": "It’s your first day on {server}!",
"account.join_modal.other": "{name} joined {server} on",
"account.join_modal.other_today": "It’s {name}’s first day on {server}!",
"account.join_modal.share.celebrate": "Share a celebratory post",
"account.join_modal.share.intro": "Share an intro post",
"account.join_modal.share.welcome": "Share a welcome post",
"account.join_modal.years": "{number, plural, one {year} other {years}}",
"account.joined_short": "Joined",
"account.languages": "Change subscribed languages",
"account.last_active": "Last active",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Manage accounts",
"collections.mark_as_sensitive": "Mark as sensitive",
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
"collections.maximum_collection_count_description": "Your server allows creation of up to {count} collections.",
"collections.maximum_collection_count_reached": "You have created the maximum number of collections",
"collections.name_length_hint": "40 characters limit",
"collections.new_collection": "New collection",
"collections.no_collections_yet": "No collections yet.",
@@ -1184,6 +1196,7 @@
"server_banner.active_users": "active users",
"server_banner.administered_by": "Administered by:",
"server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.",
"server_banner.more_about_this_server": "More about this server",
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.",
@@ -71,6 +71,16 @@
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar adhesiones de @{name}",
"account.in_memoriam": "Cuenta conmemorativa.",
"account.join_modal.day": "Día",
"account.join_modal.me": "Te uniste a {server} el",
"account.join_modal.me_anniversary": "¡Feliz Fediversario! Te uniste a {server} el",
"account.join_modal.me_today": "¡Es tu primer día en {server}!",
"account.join_modal.other": "{name} se unió a {server} el",
"account.join_modal.other_today": "¡Es el primer día de {name} en {server}!",
"account.join_modal.share.celebrate": "Compartí un mensaje conmemorativo",
"account.join_modal.share.intro": "Compartí un mensaje introductorio",
"account.join_modal.share.welcome": "Compartí un mensaje de bienvenida",
"account.join_modal.years": "{number, plural, one {# año} other {# años}}",
"account.joined_short": "En este servidor desde el",
"account.languages": "Cambiar idiomas suscritos",
"account.last_active": "Última actividad",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Administrar cuentas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción de la colección y las cuentas detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
"collections.maximum_collection_count_description": "Tu servidor permite creaciones de hasta {count} colecciones.",
"collections.maximum_collection_count_reached": "Creaste el número máximo de colecciones",
"collections.name_length_hint": "Límite de 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones aún.",
@@ -71,6 +71,16 @@
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar impulsos de @{name}",
"account.in_memoriam": "En memoria.",
"account.join_modal.day": "Día",
"account.join_modal.me": "Te uniste a {server} el",
"account.join_modal.me_anniversary": "¡Feliz Fediversario! Te uniste a {server} el",
"account.join_modal.me_today": "¡Es tu primer día en {server}!",
"account.join_modal.other": "{name} se unió a {server} el",
"account.join_modal.other_today": "¡Es el primer día de {name} en {server}!",
"account.join_modal.share.celebrate": "Compartir una publicación para celebrarlo",
"account.join_modal.share.intro": "Compartir una publicación de presentación",
"account.join_modal.share.welcome": "Compartir una publicación de bienvenida",
"account.join_modal.years": "{number, plural, one {año} other {# años}}",
"account.joined_short": "Se unió",
"account.languages": "Cambiar idiomas suscritos",
"account.last_active": "Última actividad",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Administrar cuentas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción y las cuentas de la colección detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
"collections.maximum_collection_count_description": "Tu servidor permite la creación de hasta {count} colecciones.",
"collections.maximum_collection_count_reached": "Has creado el número máximo de colecciones",
"collections.name_length_hint": "Limitado a 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "No hay colecciones todavía.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar impulsos de @{name}",
"account.in_memoriam": "Cuenta conmemorativa.",
"account.join_modal.day": "Día",
"account.join_modal.me": "Te uniste a {server} el",
"account.join_modal.me_anniversary": "¡Feliz Fediversario! Te uniste a {server} el",
"account.join_modal.me_today": "¡Es tu primer día en {server}!",
"account.join_modal.other": "{name} se unió a {server} el",
"account.join_modal.other_today": "¡Es el primer día de {name} en {server}!",
"account.join_modal.share.celebrate": "Compartir una publicación para celebrarlo",
"account.join_modal.share.intro": "Compartir una publicación de presentación",
"account.join_modal.share.welcome": "Compartir una publicación de bienvenida",
"account.join_modal.years": "{number, plural, one {año} other {# años}}",
"account.joined_short": "Se unió",
"account.languages": "Cambiar idiomas suscritos",
"account.last_active": "Última actividad",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Administrar cuentas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta la descripción de la colección y las cuentas detrás de una advertencia de contenido. El nombre de la colección seguirá siendo visible.",
"collections.maximum_collection_count_description": "Tu servidor permite la creación de hasta {count} colecciones.",
"collections.maximum_collection_count_reached": "Has creado el número máximo de colecciones",
"collections.name_length_hint": "Límite de 40 caracteres",
"collections.new_collection": "Nueva colección",
"collections.no_collections_yet": "Aún no hay colecciones.",
+4
View File
@@ -71,6 +71,10 @@
"account.go_to_profile": "Vaata profiili",
"account.hide_reblogs": "Peida @{name} jagamised",
"account.in_memoriam": "In Memoriam.",
"account.join_modal.day": "Päev",
"account.join_modal.me": "Ühinesid {server} kohas",
"account.join_modal.me_anniversary": "Rõõmsat Födiversumist! Ühinesid {server} kohas",
"account.join_modal.me_today": "See on sinu esimene päev kohas {server}!",
"account.joined_short": "Liitus",
"account.languages": "Muuda tellitud keeli",
"account.last_active": "Viimati aktiivne",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Siirry profiiliin",
"account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset",
"account.in_memoriam": "Muistoissamme.",
"account.join_modal.day": "päivä",
"account.join_modal.me": "Liityit palvelimelle {server}",
"account.join_modal.me_anniversary": "Hyvää fediversumin vuosipäivää! Liityit palvelimelle {server}",
"account.join_modal.me_today": "On ensimmäinen päiväsi palvelimella {server}!",
"account.join_modal.other": "{name} liittyi palvelimelle {server}",
"account.join_modal.other_today": "On käyttäjän {name} ensimmäinen päivä palvelimella {server}!",
"account.join_modal.share.celebrate": "Jaa juhlajulkaisu",
"account.join_modal.share.intro": "Jaa esittelyjulkaisu",
"account.join_modal.share.welcome": "Jaa tervetulojulkaisu",
"account.join_modal.years": "{number, plural, one {vuosi} other {vuotta}}",
"account.joined_short": "Liittynyt",
"account.languages": "Vaihda tilattuja kieliä",
"account.last_active": "Viimeksi aktiivisena",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Hallitse tilejä",
"collections.mark_as_sensitive": "Merkitse arkaluonteiseksi",
"collections.mark_as_sensitive_hint": "Piilottaa kokoelman kuvauksen ja tilit sisältövaroituksen taakse. Kokoelman nimi jää esiin.",
"collections.maximum_collection_count_description": "Palvelimesi sallii enintään {count} kokoelman luomisen.",
"collections.maximum_collection_count_reached": "Olet luonut enimmäismäärän kokoelmia",
"collections.name_length_hint": "40 merkin rajoitus",
"collections.new_collection": "Uusi kokoelma",
"collections.no_collections_yet": "Ei vielä kokoelmia.",
+4 -4
View File
@@ -327,8 +327,8 @@
"block_modal.title": "Bloquer l'utilisateur·ice ?",
"block_modal.you_wont_see_mentions": "Vous ne verrez plus les messages qui le ou la mentionnent.",
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour sauter ceci la prochaine fois",
"boost_modal.reblog": "Booster le message ?",
"boost_modal.undo_reblog": "Annuler le boost du message ?",
"boost_modal.reblog": "Partager le message ?",
"boost_modal.undo_reblog": "Annuler le partage du message ?",
"bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur",
"bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela pourrait être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.",
"bundle_column_error.error.title": "Oh non!",
@@ -343,7 +343,7 @@
"bundle_modal_error.retry": "Réessayer",
"callout.dismiss": "Rejeter",
"carousel.current": "<sr>Diapositive</sr> {current, number} / {max, number}",
"carousel.slide": "Diapositive {current, number} de {max, number}",
"carousel.slide": "Diapositive {current, number} sur {max, number}",
"character_counter.recommended": "{currentLength}/{maxLength} caractères recommandés",
"character_counter.required": "{currentLength}/{maxLength} caractères",
"closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.",
@@ -949,7 +949,7 @@
"notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
"notification.quoted_update": "{name} a modifié un message que vous avez cité",
"notification.reblog": "{name} a boosté votre message",
"notification.reblog.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont boosté votre message",
"notification.reblog.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont partagé votre message",
"notification.relationships_severance_event": "Connexions perdues avec {name}",
"notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.",
"notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.",
+4 -4
View File
@@ -327,8 +327,8 @@
"block_modal.title": "Bloquer l'utilisateur·ice ?",
"block_modal.you_wont_see_mentions": "Vous ne verrez plus les messages qui le ou la mentionnent.",
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois",
"boost_modal.reblog": "Booster le message ?",
"boost_modal.undo_reblog": "Annuler le boost du message ?",
"boost_modal.reblog": "Partager le message ?",
"boost_modal.undo_reblog": "Annuler le partage du message ?",
"bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur",
"bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela peut être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.",
"bundle_column_error.error.title": "Oh non !",
@@ -343,7 +343,7 @@
"bundle_modal_error.retry": "Réessayer",
"callout.dismiss": "Rejeter",
"carousel.current": "<sr>Diapositive</sr> {current, number} / {max, number}",
"carousel.slide": "Diapositive {current, number} de {max, number}",
"carousel.slide": "Diapositive {current, number} sur {max, number}",
"character_counter.recommended": "{currentLength}/{maxLength} caractères recommandés",
"character_counter.required": "{currentLength}/{maxLength} caractères",
"closed_registrations.other_server_instructions": "Puisque Mastodon est décentralisé, vous pouvez créer un compte sur un autre serveur et interagir quand même avec celui-ci.",
@@ -949,7 +949,7 @@
"notification.poll": "Un sondage auquel vous avez participé vient de se terminer",
"notification.quoted_update": "{name} a modifié un message que vous avez cité",
"notification.reblog": "{name} a partagé votre message",
"notification.reblog.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont boosté votre message",
"notification.reblog.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont partagé votre message",
"notification.relationships_severance_event": "Connexions perdues avec {name}",
"notification.relationships_severance_event.account_suspension": "Un·e administrateur·rice de {from} a suspendu {target}, ce qui signifie que vous ne pourrez plus recevoir de mises à jour ou interagir avec lui.",
"notification.relationships_severance_event.domain_block": "Un·e administrateur·rice de {from} en a bloqué {target}, comprenant {followersCount} de vos abonné·e·s et {followingCount, plural, one {# compte} other {# comptes}} vous suivez.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Téigh go dtí próifíl",
"account.hide_reblogs": "Folaigh moltaí ó @{name}",
"account.in_memoriam": "Ón tseanaimsir.",
"account.join_modal.day": "Lá",
"account.join_modal.me": "Chuaigh tú isteach i {server} ar",
"account.join_modal.me_anniversary": "Lá Féile sona duit! Chuaigh tú isteach i {server} ar",
"account.join_modal.me_today": "Seo é do chéad lá ar {server}!",
"account.join_modal.other": "Chuaigh {name} isteach i {server} ar",
"account.join_modal.other_today": "Seo é an chéad lá ag {name} ar {server}!",
"account.join_modal.share.celebrate": "Roinn post ceiliúrtha",
"account.join_modal.share.intro": "Comhroinn post réamhrá",
"account.join_modal.share.welcome": "Roinn post fáilte",
"account.join_modal.years": "{number, plural, one {bliain} two {blianta} few {blianta} many {blianta} other {blianta}}",
"account.joined_short": "Cláraithe",
"account.languages": "Athraigh teangacha foscríofa",
"account.last_active": "Gníomhach deireanach",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Bainistigh cuntais",
"collections.mark_as_sensitive": "Marcáil mar íogair",
"collections.mark_as_sensitive_hint": "Folaíonn sé cur síos agus cuntais an bhailiúcháin taobh thiar de rabhadh ábhair. Beidh ainm an bhailiúcháin le feiceáil fós.",
"collections.maximum_collection_count_description": "Ceadaíonn do fhreastalaí cruthú suas le {count} bailiúchán.",
"collections.maximum_collection_count_reached": "Tá an líon uasta bailiúcháin cruthaithe agat",
"collections.name_length_hint": "Teorainn 40 carachtar",
"collections.new_collection": "Bailiúchán nua",
"collections.no_collections_yet": "Gan aon bhailiúcháin fós.",
+16
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Ir ao perfil",
"account.hide_reblogs": "Agochar promocións de @{name}",
"account.in_memoriam": "Lembranzas.",
"account.join_modal.day": "Día",
"account.join_modal.me": "Unícheste a {server} o",
"account.join_modal.me_anniversary": "Feliz Fediversario! Chegaches a {server} o",
"account.join_modal.me_today": "É o teu primeiro día en {server}!",
"account.join_modal.other": "{name} uníuse a {server} o",
"account.join_modal.other_today": "É o primeiro día de {name} en {server}!",
"account.join_modal.share.celebrate": "Comparte unha mensaxe de celebración",
"account.join_modal.share.intro": "Comparte unha mensaxe de presentación",
"account.join_modal.share.welcome": "Comparte unha mensaxe de benvida",
"account.join_modal.years": "{number, plural, one {ano} other {anos}}",
"account.joined_short": "Uniuse",
"account.languages": "Modificar os idiomas subscritos",
"account.last_active": "Última actividade",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Xestionar contas",
"collections.mark_as_sensitive": "Marcar como sensible",
"collections.mark_as_sensitive_hint": "Oculta a descrición e contas da colección detrás dun aviso sobre o contido. O nome da colección permanece visible.",
"collections.maximum_collection_count_description": "O teu servidor permíteche crear ata {count} coleccións.",
"collections.maximum_collection_count_reached": "Creaches o número máximo de coleccións",
"collections.name_length_hint": "Límite de 40 caracteres",
"collections.new_collection": "Nova colección",
"collections.no_collections_yet": "Aínda non tes coleccións.",
@@ -410,6 +422,10 @@
"collections.search_accounts_max_reached": "Acadaches o máximo de contas permitidas",
"collections.sensitive": "Sensible",
"collections.share_short": "Compartir",
"collections.suggestions.can_not_add": "Non se pode engadir",
"collections.suggestions.can_not_add_desc": "Estas contas optaron por poder ser engadidas, ou pode que estean nun servidor que aínda non é compatible coas coleccións.",
"collections.suggestions.must_follow": "Primeiro tes que seguila",
"collections.suggestions.must_follow_desc": "Estas contas revisan todas as solicitudes de seguimento. As seguidoras poden engadilas a coleccións.",
"collections.topic_hint": "Engadir un cancelo para que axudar a que outras persoas coñezan a temática desta colección.",
"collections.topic_special_chars_hint": "Vanse eliminar os caracteres especiais ao gardar",
"collections.unlisted_collections_description": "Estas non se mostran no teu perfil, pero calquera que coñeza a ligazón pode velas.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Fara í notandasnið",
"account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
"account.in_memoriam": "Minning.",
"account.join_modal.day": "daginn",
"account.join_modal.me": "Þú skráðir þig á {server} þann",
"account.join_modal.me_anniversary": "Til hamingju með daginn! Þú skráðir þig á {server} þann",
"account.join_modal.me_today": "Þetta er fyrsti dagurinn þinn á {server}!",
"account.join_modal.other": "{name} skráði sig á {server} þann",
"account.join_modal.other_today": "Þetta er fyrsti dagurinn hjá {name} á {server}!",
"account.join_modal.share.celebrate": "Deila fagnaðarfærslu",
"account.join_modal.share.intro": "Deila kynningarfærslu",
"account.join_modal.share.welcome": "Deila móttökufærslu",
"account.join_modal.years": "{number, plural, one {ár} other {ár}}",
"account.joined_short": "Gerðist þátttakandi",
"account.languages": "Breyta tungumálum í áskrift",
"account.last_active": "Síðasta virkni",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Sýsla með notandaaðganga",
"collections.mark_as_sensitive": "Merkja sem viðkvæmt",
"collections.mark_as_sensitive_hint": "Felur lýsingu safnsins og notendur á bakvið aðvörun vegna efnis. Nafn safnsins verður áfram sýnilegt.",
"collections.maximum_collection_count_description": "Netþjónninn þinn leyfir gerð á allt að {count} söfnum.",
"collections.maximum_collection_count_reached": "Þú hefur útbúið leyfilegan hámarksfjölda safna",
"collections.name_length_hint": "40 stafa takmörk",
"collections.new_collection": "Nýtt safn",
"collections.no_collections_yet": "Engin söfn ennþá.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Vai al profilo",
"account.hide_reblogs": "Nascondi condivisioni da @{name}",
"account.in_memoriam": "In memoria.",
"account.join_modal.day": "Giorno",
"account.join_modal.me": "Ti sei iscritto/a su {server} il",
"account.join_modal.me_anniversary": "Buon Fediversario! Ti sei iscritto/a su {server} il",
"account.join_modal.me_today": "È il tuo primo giorno su {server}!",
"account.join_modal.other": "{name} si è iscritto/a su {server} il",
"account.join_modal.other_today": "È il primo giorno di {name} su {server}!",
"account.join_modal.share.celebrate": "Condividi un post celebrativo",
"account.join_modal.share.intro": "Condividi un post introduttivo",
"account.join_modal.share.welcome": "Condividi un post di benvenuto",
"account.join_modal.years": "{number, plural, one {anno} other {anni}}",
"account.joined_short": "Iscritto",
"account.languages": "Modifica le lingue d'iscrizione",
"account.last_active": "Ultima attività",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Gestisci account",
"collections.mark_as_sensitive": "Segna come sensibile",
"collections.mark_as_sensitive_hint": "Nasconde la descrizione e gli account della collezione dietro un avviso di contenuto. Il nome della collezione rimarrà visibile.",
"collections.maximum_collection_count_description": "Il tuo server consente la creazione di un massimo di {count} collezioni.",
"collections.maximum_collection_count_reached": "Hai creato il numero massimo di collezioni",
"collections.name_length_hint": "Limite di 40 caratteri",
"collections.new_collection": "Nuova collezione",
"collections.no_collections_yet": "Nessuna collezione ancora.",
+7
View File
@@ -231,6 +231,7 @@
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Mdel",
"bundle_modal_error.retry": "Ɛreḍ tikelt-nniḍen",
"callout.dismiss": "Zgel-it",
"closed_registrations_modal.description": "Asnulfu n umiḍan deg {domain} mačči d ayen izemren ad yili, maca ttxil-k·m, err deg lbal-ik·im belli ur teḥwaǧeḍ ara amiḍan s wudem ibanen ɣef {domain} akken ad tesqedceḍ Mastodon.",
"closed_registrations_modal.find_another_server": "Aff-d aqeddac nniḍen",
"closed_registrations_modal.title": "Ajerred deg Masṭudun",
@@ -241,6 +242,7 @@
"collection.share_modal.title_new": "Zuzer talkensit-ik·im tamaynut!",
"collections.account_count": "{count, plural, one {# n umiḍan} other {# n imiḍanen}}",
"collections.accounts.empty_title": "Talkensit-a d tilemt",
"collections.block_collection_owner": "Sewḥel amiḍan",
"collections.by_account": "sɣur {account_handle}",
"collections.collection_description": "Aglam",
"collections.collection_language": "Tutlayt",
@@ -248,6 +250,7 @@
"collections.collection_name": "Isem",
"collections.collection_topic": "Asentel",
"collections.continue": "Kemmel",
"collections.copy_link": "Nɣel aseɣwen",
"collections.create.steps": "Asurif wis {step}/{total}",
"collections.create_collection": "Snulfu-d talkensit",
"collections.delete_collection": "Kkes talkensit",
@@ -259,12 +262,16 @@
"collections.detail.sensitive_content": "Agbur amḥulfu",
"collections.detail.share": "Zuzer talkensit-a",
"collections.edit_details": "Ẓreg talqayt",
"collections.hidden_accounts_link": "{count, plural, one {# n umiḍan uffir} other {# n imiḍanen yettwaffaren}}",
"collections.hints.accounts_counter": "{count}/{max} n imiḍanen",
"collections.manage_accounts": "Sefrek imiḍanen",
"collections.name_length_hint": "talast n 40 n yisekkilen",
"collections.new_collection": "Talkensit tamaynut",
"collections.no_collections_yet": "Ur ɛad llant tilkensa.",
"collections.remove_account": "Kkes",
"collections.report_collection": "Cetki ɣef telkensit-a",
"collections.revoke_collection_inclusion": "Kkes-iyi seg telkensit-a",
"collections.share_short": "Bḍu",
"collections.view_collection": "Wali talkensit",
"collections.visibility_public": "Azayaz",
"collections.visibility_title": "Abani",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Profile git",
"account.hide_reblogs": "@{name} kişisinin yeniden paylaşımlarını gizle",
"account.in_memoriam": "Hatırasına.",
"account.join_modal.day": "Gün",
"account.join_modal.me": "{server} sunucusuna katıldığınız tarih",
"account.join_modal.me_anniversary": "Fediversary kutlu olsun! {server} sunucusuna katıldığınız tarih",
"account.join_modal.me_today": "{server} sunucusunda ilk gününüz!",
"account.join_modal.other": "{name}, {server} sunucusuna katıldığı tarih",
"account.join_modal.other_today": "{name} kişisinin {server} sunucusunda ilk günü!",
"account.join_modal.share.celebrate": "Kutlama amaçlı bir paylaşım yapın",
"account.join_modal.share.intro": "Tanıtım için bir paylaşım yapın",
"account.join_modal.share.welcome": "Bir hoşgeldiniz paylaşımı yapın",
"account.join_modal.years": "{number, plural, one {yıl} other {yıl}}",
"account.joined_short": "Katıldı",
"account.languages": "Abone olunan dilleri değiştir",
"account.last_active": "Son etkin",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Hesapları yönet",
"collections.mark_as_sensitive": "Hassas olarak işaretle",
"collections.mark_as_sensitive_hint": "Koleksiyonun açıklamasını ve hesaplarını içerik uyarısının arkasında gizler. Koleksiyon adı hala görünür olacaktır.",
"collections.maximum_collection_count_description": "Sunucusunuz en fazla {count} koleksiyon oluşturmanıza izin veriyor.",
"collections.maximum_collection_count_reached": "Maksimum koleksiyon sayısına ulaştınız",
"collections.name_length_hint": "40 karakterle sınırlı",
"collections.new_collection": "Yeni koleksiyon",
"collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.",
+12
View File
@@ -71,6 +71,16 @@
"account.go_to_profile": "Xem hồ sơ",
"account.hide_reblogs": "Ẩn tút @{name} đăng lại",
"account.in_memoriam": "Tưởng Niệm.",
"account.join_modal.day": "Ngày",
"account.join_modal.me": "Bạn đã tham gia {server} vào",
"account.join_modal.me_anniversary": "Happy Fediversary! Bạn đã tham gia {server} vào",
"account.join_modal.me_today": "Đó là ngày đầu tiên của bạn trên {server}!",
"account.join_modal.other": "{name} đã tham gia {server} vào",
"account.join_modal.other_today": "Đó là ngày đầu tiên của {name} trên {server}!",
"account.join_modal.share.celebrate": "Chia sẻ một tút kỷ niệm",
"account.join_modal.share.intro": "Chia sẻ một tút giới thiệu",
"account.join_modal.share.welcome": "Chia sẻ một tút chào mừng",
"account.join_modal.years": "{number, plural, other {năm}}",
"account.joined_short": "Năm",
"account.languages": "Đổi ngôn ngữ mong muốn",
"account.last_active": "Hoạt động lần cuối",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "Quản lý tài khoản",
"collections.mark_as_sensitive": "Đánh dấu nhạy cảm",
"collections.mark_as_sensitive_hint": "Ẩn phần mô tả và các tài khoản của gói khởi đầu phía sau cảnh báo nội dung. Tên gói khởi đầu vẫn hiển thị.",
"collections.maximum_collection_count_description": "Máy chủ của bạn cho phép tạo tối đa {count} gói khởi đầu.",
"collections.maximum_collection_count_reached": "Bạn đã đạt đến số lượng gói khởi đầu tối đa",
"collections.name_length_hint": "Giới hạn 40 ký tự",
"collections.new_collection": "Gói khởi đầu mới",
"collections.no_collections_yet": "Chưa có gói khởi đầu.",
@@ -71,6 +71,16 @@
"account.go_to_profile": "前往个人资料页",
"account.hide_reblogs": "隐藏来自 @{name} 的转嘟",
"account.in_memoriam": "谨此悼念。",
"account.join_modal.day": "天",
"account.join_modal.me": "你加入 {server} 的时间为",
"account.join_modal.me_anniversary": "加入 Mastodon 一周年快乐!你加入 {server} 的时间为",
"account.join_modal.me_today": "这是你加入 {server} 的第一天!",
"account.join_modal.other": "{name} 加入 {server} 的时间为",
"account.join_modal.other_today": "这是 {name} 加入 {server} 的第一天!",
"account.join_modal.share.celebrate": "分享纪念嘟文",
"account.join_modal.share.intro": "分享介绍嘟文",
"account.join_modal.share.welcome": "分享欢迎嘟文",
"account.join_modal.years": "{number, plural, other {年}}",
"account.joined_short": "加入于",
"account.languages": "更改订阅语言",
"account.last_active": "上次活跃",
@@ -398,6 +408,8 @@
"collections.manage_accounts": "管理账号",
"collections.mark_as_sensitive": "标记为敏感内容",
"collections.mark_as_sensitive_hint": "将此收藏列表的说明用内容警告隐藏。此收藏列表的名称仍将可见。",
"collections.maximum_collection_count_description": "你的服务器最多只支持创建 {count} 个收藏列表。",
"collections.maximum_collection_count_reached": "你创建的收藏列表数量已达上限",
"collections.name_length_hint": "40 字限制",
"collections.new_collection": "新建收藏列表",
"collections.no_collections_yet": "尚无收藏列表。",
@@ -71,6 +71,16 @@
"account.go_to_profile": "前往個人檔案",
"account.hide_reblogs": "隱藏來自 @{name} 之轉嘟",
"account.in_memoriam": "謹此悼念。",
"account.join_modal.day": "日",
"account.join_modal.me": "您加入 {server} 的時間為",
"account.join_modal.me_anniversary": "聯邦宇宙週年快了!您加入 {server} 的時間為",
"account.join_modal.me_today": "這是您在 {server} 的第一天!",
"account.join_modal.other": "{name} 加入 {server} 的時間為",
"account.join_modal.other_today": "這是 {name} 在 {server} 的第一天!",
"account.join_modal.share.celebrate": "分享慶祝嘟文",
"account.join_modal.share.intro": "分享介紹嘟文",
"account.join_modal.share.welcome": "分享歡迎嘟文",
"account.join_modal.years": "{number, plural, other {年}}",
"account.joined_short": "加入時間",
"account.languages": "變更訂閱的語言",
"account.last_active": "上次活躍時間",
+3
View File
@@ -1,5 +1,6 @@
import type { RecordOf } from 'immutable';
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
import type { ApiPreviewCardJSON } from 'mastodon/api_types/statuses';
export type { StatusVisibility } from 'mastodon/api_types/statuses';
@@ -10,3 +11,5 @@ export type Status = Immutable.Map<string, unknown>;
export type Card = RecordOf<ApiPreviewCardJSON>;
export type MediaAttachment = Immutable.Map<string, unknown>;
export type CollectionAttachment = RecordOf<ApiCollectionJSON>;
+4 -1
View File
@@ -610,7 +610,10 @@ export const composeReducer = (state = initialState, action) => {
case COMPOSE_LANGUAGE_CHANGE:
return state.set('language', action.language);
case COMPOSE_FOCUS:
return state.set('focusDate', new Date()).update('text', text => text.length > 0 ? text : action.defaultText);
return state
.set('focusDate', new Date())
.update('text', text => text.length > 0 ? text : action.defaultText)
.update('caretPosition', position => action.caretStart ? 0 : position);
case COMPOSE_CHANGE_MEDIA_ORDER:
return state.update('media_attachments', list => {
const indexA = list.findIndex(x => x.get('id') === action.a);
@@ -0,0 +1,14 @@
export function compareUrls(href1: string, href2: string) {
try {
const url1 = new URL(href1);
const url2 = new URL(href2);
return (
url1.origin === url2.origin &&
url1.pathname === url2.pathname &&
url1.search === url2.search
);
} catch {
return false;
}
}
@@ -1499,8 +1499,9 @@ body > [data-popper-placement] {
.media-gallery,
.video-player,
.audio-player,
.attachment-list {
margin-top: 16px;
.attachment-list,
.collection-preview {
margin-top: 14px;
}
&--in-thread {
@@ -1517,6 +1518,7 @@ body > [data-popper-placement] {
& > .picture-in-picture-placeholder,
& > .more-from-author,
& > .status-card,
& > .collection-preview,
& > .hashtag-bar,
& > .content-warning,
& > .filter-warning,
@@ -1787,7 +1789,8 @@ body > [data-popper-placement] {
.media-gallery,
.video-player,
.audio-player {
.audio-player,
.collection-preview {
margin-top: 16px;
}
@@ -6367,7 +6370,7 @@ a.status-card {
&__top {
border-radius: 16px 16px 0 0;
border-bottom: 0;
border-bottom-width: 0;
gap: 16px;
}