Merge branch 'chats' into alex-chats

This commit is contained in:
Justin
2022-09-08 08:37:58 -04:00
58 changed files with 434 additions and 178 deletions

View File

@@ -23,6 +23,5 @@
</ol>
<h1 id="opensource">Open Source Software</h1>
<p>Soapbox is free and open source (FOSS) software that runs atop a Pleroma server</p>
<p>The Soapbox repository can be found at <a href="https://gitlab.com/soapbox-pub/soapbox-fe">Soapbox-fe</a></p>
<p>The Pleroma server repository can be found at <a href="https://git.pleroma.social/pleroma/pleroma">Pleroma-be</a></p>
<p>Soapbox is free and open source (FOSS) software.</p>
<p>The Soapbox repository can be found at <a href="https://gitlab.com/soapbox-pub/soapbox">Soapbox</a></p>

View File

@@ -106,10 +106,10 @@ export function importFetchedStatus(status: APIEntity, idempotencyKey?: string)
const isBroken = (status: APIEntity) => {
try {
// Skip empty accounts
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/424
// https://gitlab.com/soapbox-pub/soapbox/-/issues/424
if (!status.account.id) return true;
// Skip broken reposts
// https://gitlab.com/soapbox-pub/soapbox/-/issues/28
// https://gitlab.com/soapbox-pub/rebased/-/issues/28
if (status.reblog && !status.reblog.account.id) return true;
return false;
} catch (e) {

View File

@@ -101,7 +101,7 @@ const editStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootS
api(getState).get(`/api/v1/statuses/${id}/source`).then(response => {
dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS });
dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text, false));
dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text, response.data.content_type, false));
dispatch(openModal('COMPOSE'));
}).catch(error => {
dispatch({ type: STATUS_FETCH_SOURCE_FAIL, error });

View File

@@ -8,7 +8,7 @@ import { useAppSelector, useOnScreen } from 'soapbox/hooks';
import { getAcct } from 'soapbox/utils/accounts';
import { displayFqn } from 'soapbox/utils/state';
import RelativeTimestamp from './relative_timestamp';
import RelativeTimestamp from './relative-timestamp';
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
import type { Account as AccountEntity } from 'soapbox/types/entities';
@@ -54,7 +54,7 @@ interface IAccount {
id?: string,
onActionClick?: (account: any) => void,
showProfileHoverCard?: boolean,
timestamp?: string | Date,
timestamp?: string,
timestampUrl?: string,
futureTimestamp?: boolean,
withAccountNote?: boolean,

View File

@@ -6,7 +6,7 @@ import { useSoapboxConfig } from 'soapbox/hooks';
import { getAcct } from '../utils/accounts';
import Icon from './icon';
import RelativeTimestamp from './relative_timestamp';
import RelativeTimestamp from './relative-timestamp';
import VerificationBadge from './verification_badge';
import type { Account } from 'soapbox/types/entities';

View File

@@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { fetchPoll, vote } from 'soapbox/actions/polls';
import { useAppDispatch } from 'soapbox/hooks';
import RelativeTimestamp from '../relative_timestamp';
import RelativeTimestamp from '../relative-timestamp';
import { Button, HStack, Stack, Text, Tooltip } from '../ui';
import type { Selected } from './poll';
@@ -54,7 +54,7 @@ const PollFooter: React.FC<IPollFooter> = ({ poll, showResults, selected }): JSX
</Button>
)}
<HStack space={1.5} alignItems='center'>
<HStack space={1.5} alignItems='center' wrap>
{poll.pleroma.get('non_anonymous') && (
<>
<Tooltip text={intl.formatMessage(messages.nonAnonymous)}>

View File

@@ -18,7 +18,7 @@ interface IPoll {
}
const messages = defineMessages({
multiple: { id: 'poll.chooseMultiple', defaultMessage: 'Choose as many as you\'d like.' },
multiple: { id: 'poll.choose_multiple', defaultMessage: 'Choose as many as you\'d like.' },
});
const Poll: React.FC<IPoll> = ({ id, status }): JSX.Element | null => {

View File

@@ -1,8 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import { injectIntl, defineMessages } from 'react-intl';
import { injectIntl, defineMessages, IntlShape, FormatDateOptions } from 'react-intl';
import { Text } from './ui';
import Text, { IText } from './ui/text/text';
const messages = defineMessages({
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
@@ -17,7 +16,7 @@ const messages = defineMessages({
days_remaining: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' },
});
const dateFormatOptions = {
const dateFormatOptions: FormatDateOptions = {
hour12: false,
year: 'numeric',
month: 'short',
@@ -26,7 +25,7 @@ const dateFormatOptions = {
minute: '2-digit',
};
const shortDateFormatOptions = {
const shortDateFormatOptions: FormatDateOptions = {
month: 'short',
day: 'numeric',
};
@@ -38,7 +37,7 @@ const DAY = 1000 * 60 * 60 * 24;
const MAX_DELAY = 2147483647;
const selectUnits = delta => {
const selectUnits = (delta: number) => {
const absDelta = Math.abs(delta);
if (absDelta < MINUTE) {
@@ -52,7 +51,7 @@ const selectUnits = delta => {
return 'day';
};
const getUnitDelay = units => {
const getUnitDelay = (units: string) => {
switch (units) {
case 'second':
return SECOND;
@@ -67,7 +66,7 @@ const getUnitDelay = units => {
}
};
export const timeAgoString = (intl, date, now, year) => {
export const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) => {
const delta = now - date.getTime();
let relativeTime;
@@ -93,7 +92,7 @@ export const timeAgoString = (intl, date, now, year) => {
return relativeTime;
};
const timeRemainingString = (intl, date, now) => {
const timeRemainingString = (intl: IntlShape, date: Date, now: number) => {
const delta = date.getTime() - now;
let relativeTime;
@@ -113,16 +112,21 @@ const timeRemainingString = (intl, date, now) => {
return relativeTime;
};
export default @injectIntl
class RelativeTimestamp extends React.Component {
interface RelativeTimestampProps extends IText {
intl: IntlShape,
timestamp: string,
year?: number,
futureDate?: boolean,
}
static propTypes = {
intl: PropTypes.object.isRequired,
timestamp: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
theme: PropTypes.string,
futureDate: PropTypes.bool,
};
interface RelativeTimestampState {
now: number,
}
/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */
class RelativeTimestamp extends React.Component<RelativeTimestampProps, RelativeTimestampState> {
_timer: NodeJS.Timeout | undefined;
state = {
now: Date.now(),
@@ -130,10 +134,10 @@ class RelativeTimestamp extends React.Component {
static defaultProps = {
year: (new Date()).getFullYear(),
theme: 'inherit',
theme: 'inherit' as const,
};
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: RelativeTimestampProps, nextState: RelativeTimestampState) {
// As of right now the locale doesn't change without a new page load,
// but we might as well check in case that ever changes.
return this.props.timestamp !== nextProps.timestamp ||
@@ -141,14 +145,14 @@ class RelativeTimestamp extends React.Component {
this.state.now !== nextState.now;
}
UNSAFE_componentWillReceiveProps(prevProps) {
UNSAFE_componentWillReceiveProps(prevProps: RelativeTimestampProps) {
if (this.props.timestamp !== prevProps.timestamp) {
this.setState({ now: Date.now() });
}
}
componentDidMount() {
this._scheduleNextUpdate(this.props, this.state);
this._scheduleNextUpdate();
}
UNSAFE_componentWillUpdate() {
@@ -156,11 +160,15 @@ class RelativeTimestamp extends React.Component {
}
componentWillUnmount() {
clearTimeout(this._timer);
if (this._timer) {
clearTimeout(this._timer);
}
}
_scheduleNextUpdate() {
clearTimeout(this._timer);
if (this._timer) {
clearTimeout(this._timer);
}
const { timestamp } = this.props;
const delta = (new Date(timestamp)).getTime() - this.state.now;
@@ -177,8 +185,8 @@ class RelativeTimestamp extends React.Component {
render() {
const { timestamp, intl, year, futureDate, theme, ...textProps } = this.props;
const date = new Date(timestamp);
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now) : timeAgoString(intl, date, this.state.now, year);
const date = new Date(timestamp);
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now) : timeAgoString(intl, date, this.state.now, year!);
return (
<Text {...textProps} theme={theme} tag='time' title={intl.formatDate(date, dateFormatOptions)}>
@@ -188,3 +196,5 @@ class RelativeTimestamp extends React.Component {
}
}
export default injectIntl(RelativeTimestamp);

View File

@@ -6,7 +6,7 @@ import { Virtuoso, Components, VirtuosoProps, VirtuosoHandle, ListRange, IndexLo
import { useSettings } from 'soapbox/hooks';
import LoadMore from './load_more';
import { Card, Spinner, Text } from './ui';
import { Card, Spinner } from './ui';
/** Custom Viruoso component context. */
type Context = {
@@ -162,7 +162,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
{isLoading ? (
<Spinner />
) : (
<Text>{emptyMessage}</Text>
emptyMessage
)}
</Card>
</div>

View File

@@ -301,12 +301,12 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
};
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
const { url } = status;
const { uri } = status;
const textarea = document.createElement('textarea');
e.stopPropagation();
textarea.textContent = url;
textarea.textContent = uri;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);

View File

@@ -42,11 +42,13 @@ interface IHStack {
grow?: boolean,
/** Extra CSS styles for the <div> */
style?: React.CSSProperties
/** Whether to let the flexbox wrap onto multiple lines. */
wrap?: boolean,
}
/** Horizontal row of child elements. */
const HStack = forwardRef<HTMLDivElement, IHStack>((props, ref) => {
const { space, alignItems, grow, justifyContent, className, ...filteredProps } = props;
const { space, alignItems, grow, justifyContent, wrap, className, ...filteredProps } = props;
return (
<div
@@ -60,6 +62,7 @@ const HStack = forwardRef<HTMLDivElement, IHStack>((props, ref) => {
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
'flex-grow': grow,
'flex-wrap': wrap,
}, className)}
/>
);

View File

@@ -39,12 +39,13 @@ interface IStack extends React.HTMLAttributes<HTMLDivElement> {
}
/** Vertical stack of child elements. */
const Stack: React.FC<IStack> = (props) => {
const Stack: React.FC<IStack> = React.forwardRef((props, ref: React.LegacyRef<HTMLDivElement> | undefined) => {
const { space, alignItems, justifyContent, className, grow, ...filteredProps } = props;
return (
<div
{...filteredProps}
ref={ref}
className={classNames('flex flex-col', {
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
@@ -56,6 +57,6 @@ const Stack: React.FC<IStack> = (props) => {
}, className)}
/>
);
};
});
export default Stack;

View File

@@ -84,7 +84,9 @@ interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'danger
/** Whether to truncate the text if its container is too small. */
truncate?: boolean,
/** Font weight of the text. */
weight?: Weights
weight?: Weights,
/** Tooltip title. */
title?: string,
}
/** UI-friendly text container with dark mode support. */
@@ -133,4 +135,7 @@ const Text: React.FC<IText> = React.forwardRef(
},
);
export default Text;
export {
Text as default,
IText,
};

View File

@@ -115,27 +115,34 @@ const Search = (props: ISearch) => {
];
const hasValue = value.length > 0 || submitted;
const Component = autosuggest ? AutosuggestAccountInput : 'input';
const componentProps: any = {
className: 'block w-full pl-3 pr-10 py-2 border border-gray-200 dark:border-gray-800 rounded-full leading-5 bg-gray-200 dark:bg-gray-800 dark:text-white placeholder:text-gray-600 dark:placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 sm:text-sm',
type: 'text',
id: 'search',
placeholder: intl.formatMessage(messages.placeholder),
value,
onChange: handleChange,
onKeyDown: handleKeyDown,
onFocus: handleFocus,
autoFocus: autoFocus,
};
if (autosuggest) {
componentProps.onSelected = handleSelected;
componentProps.menu = makeMenu();
componentProps.autoSelect = false;
}
return (
<div className='w-full'>
<label htmlFor='search' className='sr-only'>{intl.formatMessage(messages.placeholder)}</label>
<div className='relative'>
<Component
className='block w-full pl-3 pr-10 py-2 border border-gray-200 dark:border-gray-800 rounded-full leading-5 bg-gray-200 dark:bg-gray-800 dark:text-white placeholder:text-gray-600 dark:placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 sm:text-sm'
type='text'
id='search'
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onFocus={handleFocus}
onSelected={handleSelected}
autoFocus={autoFocus}
autoSelect={false}
menu={makeMenu()}
/>
{autosuggest ? (
<AutosuggestAccountInput {...componentProps} />
) : (
<input {...componentProps} />
)}
<div
role='button'

View File

@@ -6,7 +6,7 @@ import { getSettings } from 'soapbox/actions/settings';
import Avatar from 'soapbox/components/avatar';
import DisplayName from 'soapbox/components/display-name';
import Permalink from 'soapbox/components/permalink';
import RelativeTimestamp from 'soapbox/components/relative_timestamp';
import RelativeTimestamp from 'soapbox/components/relative-timestamp';
import { Text } from 'soapbox/components/ui';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { useAppSelector } from 'soapbox/hooks';

View File

@@ -99,7 +99,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
{quote}
<HStack justifyContent='between' alignItems='center' className='py-2'>
<HStack justifyContent='between' alignItems='center' className='py-2' wrap>
<StatusInteractionBar status={actualStatus} />
<HStack space={1} alignItems='center'>

View File

@@ -1,21 +1,29 @@
import PropTypes from 'prop-types';
import React from 'react';
const emptyComponent = () => null;
const noop = () => { };
class Bundle extends React.PureComponent {
interface BundleProps {
fetchComponent: () => Promise<any>,
loading: React.ComponentType,
error: React.ComponentType<{ onRetry: (props: BundleProps) => void }>,
children: (mod: any) => React.ReactNode,
renderDelay?: number,
onFetch: () => void,
onFetchSuccess: () => void,
onFetchFail: (error: any) => void,
}
static propTypes = {
fetchComponent: PropTypes.func.isRequired,
loading: PropTypes.func,
error: PropTypes.func,
children: PropTypes.func.isRequired,
renderDelay: PropTypes.number,
onFetch: PropTypes.func,
onFetchSuccess: PropTypes.func,
onFetchFail: PropTypes.func,
}
interface BundleState {
mod: any,
forceRender: boolean,
}
/** Fetches and renders an async component. */
class Bundle extends React.PureComponent<BundleProps, BundleState> {
timeout: NodeJS.Timeout | undefined;
timestamp: Date | undefined;
static defaultProps = {
loading: emptyComponent,
@@ -37,7 +45,7 @@ class Bundle extends React.PureComponent {
this.load(this.props);
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps: BundleProps) {
if (nextProps.fetchComponent !== this.props.fetchComponent) {
this.load(nextProps);
}
@@ -49,7 +57,7 @@ class Bundle extends React.PureComponent {
}
}
load = (props) => {
load = (props: BundleProps) => {
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
const cachedMod = Bundle.cache.get(fetchComponent);
@@ -88,10 +96,10 @@ class Bundle extends React.PureComponent {
render() {
const { loading: Loading, error: Error, children, renderDelay } = this.props;
const { mod, forceRender } = this.state;
const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay;
const elapsed = this.timestamp ? ((new Date()).getTime() - this.timestamp.getTime()) : renderDelay!;
if (mod === undefined) {
return (elapsed >= renderDelay || forceRender) ? <Loading /> : null;
return (elapsed >= renderDelay! || forceRender) ? <Loading /> : null;
}
if (mod === null) {

View File

@@ -14,7 +14,7 @@ import { isRemote, getDomain } from 'soapbox/utils/accounts';
import type { ReducerAccount } from 'soapbox/reducers/accounts';
const messages = defineMessages({
addAdditionalStatuses: { id: 'report.otherActions.addAdditionl', defaultMessage: 'Would you like to add additional statuses to this report?' },
addAdditionalStatuses: { id: 'report.otherActions.addAdditional', defaultMessage: 'Would you like to add additional statuses to this report?' },
addMore: { id: 'report.otherActions.addMore', defaultMessage: 'Add more' },
furtherActions: { id: 'report.otherActions.furtherActions', defaultMessage: 'Further actions:' },
hideAdditonalStatuses: { id: 'report.otherActions.hideAdditional', defaultMessage: 'Hide additional statuses' },

View File

@@ -3,14 +3,16 @@ import { connect } from 'react-redux';
import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from '../../../actions/bundles';
import Bundle from '../components/bundle';
const mapDispatchToProps = dispatch => ({
import type { AppDispatch } from 'soapbox/store';
const mapDispatchToProps = (dispatch: AppDispatch) => ({
onFetch() {
dispatch(fetchBundleRequest());
},
onFetchSuccess() {
dispatch(fetchBundleSuccess());
},
onFetchFail(error) {
onFetchFail(error: any) {
dispatch(fetchBundleFail(error));
},
});

View File

@@ -0,0 +1,25 @@
let listener: ((rect: any) => void) | undefined = undefined;
const mockDisconnect = jest.fn();
class ResizeObserver {
constructor(ls: any) {
listener = ls;
}
observe() {
// do nothing
}
unobserve() {
// do nothing
}
disconnect() {
mockDisconnect();
}
}
// eslint-disable-next-line compat/compat
(window as any).ResizeObserver = ResizeObserver;
export { ResizeObserver as default, listener, mockDisconnect };

View File

@@ -1,21 +1,13 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { listener, mockDisconnect } from '../__mocks__/resize-observer';
import { useDimensions } from '../useDimensions';
let listener: ((rect: any) => void) | undefined = undefined;
(window as any).ResizeObserver = class ResizeObserver {
constructor(ls: any) {
listener = ls;
}
observe() {}
disconnect() {}
};
describe('useDimensions()', () => {
beforeEach(() => {
mockDisconnect.mockClear();
});
it('defaults to 0', () => {
const { result } = renderHook(() => useDimensions());
@@ -56,16 +48,6 @@ describe('useDimensions()', () => {
});
it('disconnects on unmount', () => {
const disconnect = jest.fn();
(window as any).ResizeObserver = class ResizeObserver {
observe() {}
disconnect() {
disconnect();
}
};
const { result, unmount } = renderHook(() => useDimensions());
act(() => {
@@ -73,8 +55,8 @@ describe('useDimensions()', () => {
(result.current[1] as any)(div);
});
expect(disconnect).toHaveBeenCalledTimes(0);
expect(mockDisconnect).toHaveBeenCalledTimes(0);
unmount();
expect(disconnect).toHaveBeenCalledTimes(1);
expect(mockDisconnect).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
type UseDimensionsRect = { width: number, height: number };
type UseDimensionsResult = [Element | null, any, any]
@@ -14,7 +15,7 @@ const useDimensions = (): UseDimensionsResult => {
const observer = useMemo(
() =>
new (window as any).ResizeObserver((entries: any) => {
new ResizeObserver((entries: any) => {
if (entries[0]) {
const { width, height } = entries[0].contentRect;
setRect({ width, height });

View File

@@ -73,6 +73,8 @@
"account_note.target": "Notatka o @{target}",
"account_search.placeholder": "Szukaj konta",
"account_timeline.column_settings.show_pinned": "Show pinned posts",
"actualStatus.edited": "Edytowano {date}",
"actualStatuses.quote_tombstone": "Wpis jest niedostępny",
"admin.awaiting_approval.approved_message": "Przyjęto {acct}!",
"admin.awaiting_approval.empty_message": "Nikt nie oczekuje przyjęcia. Gdy zarejestruje się nowy użytkownik, możesz zatwierdzić go tutaj.",
"admin.awaiting_approval.rejected_message": "Odrzucono {acct}!",
@@ -134,8 +136,8 @@
"admin_nav.awaiting_approval": "Oczekujące zgłoszenia",
"admin_nav.dashboard": "Panel administracyjny",
"admin_nav.reports": "Zgłoszenia",
"age_verification.header": "Wprowadź datę urodzenia",
"age_verification.fail": "Musisz mieć przynajmniej {ageMinimum, plural, one {# rok} few {# lata} many {# lat} other {# lat}}.",
"age_verification.header": "Wprowadź datę urodzenia",
"alert.unexpected.body": "Przepraszamy za niedogodności. Jeżeli problem nie ustanie, skontaktuj się z naszym wsparciem technicznym. Możesz też spróbować {clearCookies} (zostaniesz wylogowany(-a)).",
"alert.unexpected.browser": "Przeglądarka",
"alert.unexpected.clear_cookies": "wyczyścić pliki cookies i dane przeglądarki",
@@ -164,16 +166,16 @@
"app_create.scopes_placeholder": "np. „read write follow”",
"app_create.submit": "Utwórz aplikację",
"app_create.website_label": "Strona",
"auth_layout.register": "Utwórz konto",
"auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło",
"auth.logged_out": "Wylogowano.",
"auth_layout.register": "Utwórz konto",
"backups.actions.create": "Utwórz kopię zapasową",
"backups.empty_message": "Nie znaleziono kopii zapasowych. {action}",
"backups.empty_message.action": "Chcesz utworzyć?",
"backups.pending": "Oczekująca",
"beta.also_available": "Dostępne w językach:",
"birthdays_modal.empty": "Żaden z Twoich znajomych nie ma dziś urodzin.",
"birthday_panel.title": "Urodziny",
"birthdays_modal.empty": "Żaden z Twoich znajomych nie ma dziś urodzin.",
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
"bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.",
"bundle_column_error.retry": "Spróbuj ponownie",
@@ -283,8 +285,8 @@
"community.column_settings.title": "Ustawienia lokalnej osi czasu",
"compare_history_modal.header": "Historia edycji",
"compose.character_counter.title": "Wykorzystano {chars} z {maxChars} znaków",
"compose.invalid_schedule": "Musisz zaplanować wpis przynajmniej 5 minut wcześniej.",
"compose.edit_success": "Twój wpis został zedytowany",
"compose.invalid_schedule": "Musisz zaplanować wpis przynajmniej 5 minut wcześniej.",
"compose.submit_success": "Twój wpis został wysłany",
"compose_form.direct_message_warning": "Ten wpis będzie widoczny tylko dla wszystkich wspomnianych użytkowników.",
"compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.",
@@ -342,6 +344,7 @@
"confirmations.block.confirm": "Zablokuj",
"confirmations.block.heading": "Zablokuj @{name}",
"confirmations.block.message": "Czy na pewno chcesz zablokować {name}?",
"confirmations.cancel_editing.confirm": "Anuluj edycję",
"confirmations.cancel_editing.heading": "Anuluj edycję wpisu",
"confirmations.cancel_editing.message": "Czy na pewno chcesz anulować edytowanie wpisu? Niezapisane zmiany zostaną utracone.",
"confirmations.delete.confirm": "Usuń",
@@ -432,9 +435,9 @@
"edit_profile.fields.location_label": "Lokalizacja",
"edit_profile.fields.location_placeholder": "Lokalizacja",
"edit_profile.fields.locked_label": "Zablokuj konto",
"edit_profile.fields.meta_fields_label": "Pola profilu",
"edit_profile.fields.meta_fields.content_placeholder": "Treść",
"edit_profile.fields.meta_fields.label_placeholder": "Podpis",
"edit_profile.fields.meta_fields_label": "Pola profilu",
"edit_profile.fields.stranger_notifications_label": "Blokuj powiadomienia od nieznajomych",
"edit_profile.fields.website_label": "Strona internetowa",
"edit_profile.fields.website_placeholder": "Wyświetl link",
@@ -446,7 +449,7 @@
"edit_profile.hints.header": "PNG, GIF lub JPG. Zostanie zmniejszony do {size}",
"edit_profile.hints.hide_network": "To, kogo obserwujesz i kto Cię obserwuje nie będzie wyświetlane na Twoim profilu",
"edit_profile.hints.locked": "Wymaga ręcznego zatwierdzania obserwacji",
"edit_profile.hints.meta_fields": "Możesz ustawić {count, plural, one {# niestandardowe pole} few {# niestandardowe pola} many {# niestandardowych pól} wyświetlanych na Twoim profilu.",
"edit_profile.hints.meta_fields": "Możesz ustawić {count, plural, one {# niestandardowe pole wyświetlane} few {# niestandardowe pola wyświetlane} many {# niestandardowych pól wyświetlanych}} na Twoim profilu.",
"edit_profile.hints.stranger_notifications": "Wyświetlaj tylko powiadomienia od osób, które obserwujesz",
"edit_profile.save": "Zapisz",
"edit_profile.success": "Zapisano profil!",
@@ -564,10 +567,11 @@
"forms.copy": "Kopiuj",
"forms.hide_password": "Ukryj hasło",
"forms.show_password": "Pokaż hasło",
"gdpr.accept": "Aceptuj",
"gdpr.learn_more": "Dowiedz się więcej",
"gdpr.accept": "Akceptuj",
"gdpr.learn_more": "Dowiedz się więcej",
"gdpr.message": "{siteTitle} korzysta z ciasteczek sesji, które są niezbędne dla działania strony.",
"gdpr.title": "{siteTitle} korzysta z ciasteczek",
"generic.saved": "Zapisano",
"getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).",
"group.detail.archived_group": "Archived group",
"group.members.empty": "Ta grupa nie ma żadnych członków.",
@@ -625,6 +629,7 @@
"import_data.success.blocks": "Pomyślnie zaimportowano zablokowane konta",
"import_data.success.followers": "Pomyślnie zaimportowano obserwowane konta",
"import_data.success.mutes": "Pomyślnie zaimportowano wyciszone konta",
"input.copy": "Kopiuj",
"input.password.hide_password": "Ukryj hasło",
"input.password.show_password": "Pokazuj hasło",
"intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}",
@@ -831,8 +836,8 @@
"notifications.filter.statuses": "Nowe wpisy osób, które subskrybujesz",
"notifications.group": "{count, number} {count, plural, one {powiadomienie} few {powiadomienia} many {powiadomień} more {powiadomień}}",
"notifications.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowe powiadomienie} few {nowe powiadomienia} many {nowych powiadomień} other {nowe powiadomienia}}",
"oauth_consumers.title": "Inne opcje logowania",
"oauth_consumer.tooltip": "Zaloguj się używając {provider}",
"oauth_consumers.title": "Inne opcje logowania",
"onboarding.avatar.subtitle": "Just have fun with it.",
"onboarding.avatar.title": "Wybierz zdjęcie profilowe",
"onboarding.display_name.subtitle": "Możesz ją zawsze zmienić później.",
@@ -859,8 +864,10 @@
"patron.title": "Cel wsparcia",
"pinned_accounts.title": "Polecani przez {name}",
"pinned_statuses.none": "Brak przypięć do pokazania.",
"poll.chooseMultiple": "Wybierz tyle, ile potrzebujesz.",
"poll.choose_multiple": "Wybierz tyle, ile potrzebujesz.",
"poll.closed": "Zamknięte",
"poll.non_anonymous": "Publiczne głosowanie",
"poll.non_anonymous.label": "Inne instancje mogą wyświetlać, które odpowiedzi wybrałeś(-aś)",
"poll.refresh": "Odśwież",
"poll.total_people": "{count, plural, one {# osoba} few {# osoby} many {# osób} other {# osób}}",
"poll.total_votes": "{count, plural, one {# głos} few {# głosy} many {# głosów} other {# głosów}}",
@@ -889,8 +896,9 @@
"preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach",
"preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu",
"preferences.fields.theme": "Motyw",
"preferences.fields.underline_links_label": "Zawsze podkreślaj odnośniki we wpisach",
"preferences.fields.underline_links_label": "Zawsze podkreślaj odnośniki we wpisach",
"preferences.fields.unfollow_modal_label": "Pokazuj prośbę o potwierdzenie przed cofnięciem obserwacji",
"preferences.hints.demetricator": "Ogranicz skutki uzależnienia od mediów społecznościowych, ukrywając wyświetlane liczby.",
"preferences.hints.feed": "Na stronie głównej",
"preferences.notifications.advanced": "Pokazuj wszystkie kategorie powiadomień",
"preferences.options.content_type_markdown": "Markdown",
@@ -918,12 +926,6 @@
"regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
"register_invite.lead": "Wypełnij poniższy formularz, aby utworzyć konto.",
"register_invite.title": "Otrzymałeś(-aś) zaproszenie na {siteTitle}!",
"registrations.create_account": "Utwórz konto",
"registrations.error": "Nie udało się zarejestrować konta.",
"registrations.get_started": "Rozpocznijmy!",
"registrations.success": "Witamy na {siteTitle}!",
"registrations.tagline": "Media społecznościowe, które nie wykluczają",
"registrations.unprocessable_entity": "Ta nazwa użytkownika jest już zajęta.",
"registration.acceptance": "Rejestrując się, wyrażasz zgodę na {terms} i {privacy}.",
"registration.agreement": "Akceptuję {tos}.",
"registration.captcha.hint": "Naciśnij na obrazek, aby uzyskać nową captchę",
@@ -948,6 +950,12 @@
"registration.validation.capital_letter": "1 wielka litera",
"registration.validation.lowercase_letter": "1 mała litera",
"registration.validation.minimum_characters": "8 znaków",
"registrations.create_account": "Utwórz konto",
"registrations.error": "Nie udało się zarejestrować konta.",
"registrations.get_started": "Rozpocznijmy!",
"registrations.success": "Witamy na {siteTitle}!",
"registrations.tagline": "Media społecznościowe, które nie wykluczają",
"registrations.unprocessable_entity": "Ta nazwa użytkownika jest już zajęta.",
"relative_time.days": "{number} dni",
"relative_time.hours": "{number} godz.",
"relative_time.just_now": "teraz",
@@ -988,7 +996,7 @@
"report.forward": "Przekaż na {target}",
"report.forward_hint": "To konto znajduje się na innej instancji. Czy chcesz wysłać anonimową kopię zgłoszenia rnież na nią?",
"report.next": "Dalej",
"report.otherActions.addAdditionl": "Czy chcesz uwzględnić inne wpisy w tym zgłoszeniu?",
"report.otherActions.addAdditional": "Czy chcesz uwzględnić inne wpisy w tym zgłoszeniu?",
"report.otherActions.addMore": "Dodaj więcej",
"report.otherActions.furtherActions": "Dodatkowe działania:",
"report.otherActions.hideAdditional": "Ukryj dodatkowe wpisy",
@@ -1116,8 +1124,8 @@
"soapbox_config.single_user_mode_profile_hint": "@nazwa",
"soapbox_config.single_user_mode_profile_label": "Nazwa głównego użytkownika",
"soapbox_config.verified_can_edit_name_label": "Pozwól zweryfikowanym użytkownikom na zmianę swojej nazwy wyświetlanej.",
"sponsored.info.title": "Dlaczego widzę tę reklamę?",
"sponsored.info.message": "{siteTitle} wyświetla reklamy, aby utrzymać naszą usługę.",
"sponsored.info.title": "Dlaczego widzę tę reklamę?",
"sponsored.subtitle": "Wpis sponsorowany",
"status.actions.more": "Więcej",
"status.admin_account": "Otwórz interfejs moderacyjny dla @{name}",
@@ -1137,6 +1145,10 @@
"status.embed": "Osadź",
"status.favourite": "Zareaguj",
"status.filtered": "Filtrowany(-a)",
"status.in_review_summary.contact": "Jeżeli uważasz że to błąd, {link}.",
"status.in_review_summary.link": "skontaktuj się z działem pomocy",
"status.in_review_summary.summary": "Ten wpis został wysłany do weryfikacji moderatorom i jest widoczny tylko dla Ciebie.",
"status.in_review_warning": "Treści w trakcie weryfikacji",
"status.load_more": "Załaduj więcej",
"status.media_hidden": "Zawartość multimedialna ukryta",
"status.mention": "Wspomnij o @{name}",
@@ -1186,7 +1198,7 @@
"streamfield.add": "Dodaj",
"streamfield.remove": "Usuń",
"suggestions.dismiss": "Odrzuć sugestię",
"sw.update": "Aktualizacja",
"sw.update": "Aktualizacja",
"sw.update_text": "Dostępna jest aktualizacja.",
"tabs_bar.all": "Wszystkie",
"tabs_bar.apps": "Aplikacje",

View File

@@ -21,7 +21,6 @@ describe('normalizePoll()', () => {
expect(ImmutableRecord.isRecord(result)).toBe(true);
expect(ImmutableRecord.isRecord(result.options.get(0))).toBe(true);
expect(result.toJS()).toMatchObject(expected);
expect(result.expires_at instanceof Date).toBe(true);
});
it('normalizes a Pleroma logged-out poll', () => {

View File

@@ -164,7 +164,6 @@ describe('normalizeStatus()', () => {
expect(ImmutableRecord.isRecord(poll)).toBe(true);
expect(ImmutableRecord.isRecord(poll.options.get(0))).toBe(true);
expect(poll.toJS()).toMatchObject(expected);
expect(poll.expires_at instanceof Date).toBe(true);
});
it('normalizes a Pleroma logged-out poll', () => {

View File

@@ -26,7 +26,7 @@ export const AccountRecord = ImmutableRecord({
avatar_static: '',
birthday: '',
bot: false,
created_at: new Date(),
created_at: '',
discoverable: false,
display_name: '',
emojis: ImmutableList<Emoji>(),
@@ -38,7 +38,7 @@ export const AccountRecord = ImmutableRecord({
header: '',
header_static: '',
id: '',
last_status_at: new Date(),
last_status_at: '',
location: '',
locked: false,
moved: null as EmbeddedEntity<any>,
@@ -78,7 +78,7 @@ export const FieldRecord = ImmutableRecord({
value_plain: '',
});
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/549
// https://gitlab.com/soapbox-pub/soapbox/-/issues/549
const normalizePleromaLegacyFields = (account: ImmutableMap<string, any>) => {
return account.update('pleroma', ImmutableMap(), (pleroma: ImmutableMap<string, any>) => {
return pleroma.withMutations(pleroma => {

View File

@@ -21,7 +21,7 @@ import type { Emoji, PollOption } from 'soapbox/types/entities';
export const PollRecord = ImmutableRecord({
emojis: ImmutableList<Emoji>(),
expired: false,
expires_at: new Date(),
expires_at: '',
id: '',
multiple: false,
options: ImmutableList<PollOption>(),

View File

@@ -28,8 +28,8 @@ export const StatusRecord = ImmutableRecord({
bookmarked: false,
card: null as Card | null,
content: '',
created_at: new Date(),
edited_at: null as Date | null,
created_at: '',
edited_at: null as string | null,
emojis: ImmutableList<Emoji>(),
favourited: false,
favourites_count: 0,

View File

@@ -105,7 +105,7 @@ const ProfilePage: React.FC<IProfilePage> = ({ params, children }) => {
</BundleContainer>
{account && showTabs && (
<Tabs items={tabItems} activeItem={activeItem} />
<Tabs key={`profile-tabs-${account.id}`} items={tabItems} activeItem={activeItem} />
)}
{children}

View File

@@ -20,7 +20,7 @@ const importEmojis = (customEmojis: APIEntity[]) => {
const emojis = (fromJS(customEmojis) as ImmutableList<ImmutableMap<string, string>>).filter((emoji) => {
// If a custom emoji has the shortcode of a Unicode emoji, skip it.
// Otherwise it breaks EmojiMart.
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/610
// https://gitlab.com/soapbox-pub/soapbox/-/issues/610
const shortcode = emoji.get('shortcode', '').toLowerCase();
return !emojiData[shortcode];
});

View File

@@ -67,7 +67,7 @@ const fixNotification = notification => {
const isValid = notification => {
try {
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/424
// https://gitlab.com/soapbox-pub/soapbox/-/issues/424
if (!notification.account.id) {
return false;
}

View File

@@ -242,7 +242,7 @@ const timelineDisconnect = (state: State, timelineId: string) => {
if (items.isEmpty()) return;
// This is causing problems. Disable for now.
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/716
// https://gitlab.com/soapbox-pub/soapbox/-/issues/716
// timeline.set('items', addStatusId(items, null));
}));
};

View File

@@ -59,7 +59,7 @@ const findAccountsByUsername = (state: RootState, username: string) => {
const accounts = state.accounts;
return accounts.filter(account => {
return username.toLowerCase() === account.acct.toLowerCase();
return username.toLowerCase() === account?.acct.toLowerCase();
});
};

View File

@@ -46,8 +46,8 @@ export const PIXELFED = 'Pixelfed';
export const TRUTHSOCIAL = 'TruthSocial';
/**
* Soapbox BE, the recommended Pleroma fork for Soapbox.
* @see {@link https://gitlab.com/soapbox-pub/soapbox-be}
* Rebased, the recommended backend for Soapbox.
* @see {@link https://gitlab.com/soapbox-pub/rebased}
*/
export const SOAPBOX = 'soapbox';

View File

@@ -35,7 +35,7 @@ export const shouldHaveCard = (status: StatusEntity): boolean => {
};
/** Whether the media IDs on this status have integer IDs (opposed to FlakeIds). */
// https://gitlab.com/soapbox-pub/soapbox-fe/-/merge_requests/1087
// https://gitlab.com/soapbox-pub/soapbox/-/merge_requests/1087
export const hasIntegerMediaIds = (status: StatusEntity): boolean => {
return status.media_attachments.some(({ id }) => isIntegerId(id));
};