Put Typescript in "strict" mode, fix or ignore errors
This commit is contained in:
parent
84d7d2ee38
commit
2940a3ff4d
|
@ -54,6 +54,7 @@ const Account = ({
|
||||||
}: IAccount) => {
|
}: IAccount) => {
|
||||||
const overflowRef = React.useRef<HTMLDivElement>(null);
|
const overflowRef = React.useRef<HTMLDivElement>(null);
|
||||||
const actionRef = React.useRef<HTMLDivElement>(null);
|
const actionRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
// @ts-ignore
|
||||||
const isOnScreen = useOnScreen(overflowRef);
|
const isOnScreen = useOnScreen(overflowRef);
|
||||||
|
|
||||||
const [style, setStyle] = React.useState<React.CSSProperties>({ visibility: 'hidden' });
|
const [style, setStyle] = React.useState<React.CSSProperties>({ visibility: 'hidden' });
|
||||||
|
@ -62,6 +63,7 @@ const Account = ({
|
||||||
const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null);
|
const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null);
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = () => {
|
||||||
|
// @ts-ignore
|
||||||
onActionClick(account);
|
onActionClick(account);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -96,7 +98,7 @@ const Account = ({
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isOnScreen) {
|
if (isOnScreen) {
|
||||||
const style: React.CSSProperties = {};
|
const style: React.CSSProperties = {};
|
||||||
const actionWidth = actionRef.current?.clientWidth;
|
const actionWidth = actionRef.current?.clientWidth || 0;
|
||||||
|
|
||||||
if (overflowRef.current) {
|
if (overflowRef.current) {
|
||||||
style.maxWidth = overflowRef.current.clientWidth - 30 - avatarSize - actionWidth;
|
style.maxWidth = overflowRef.current.clientWidth - 30 - avatarSize - actionWidth;
|
||||||
|
|
|
@ -46,6 +46,7 @@ function Blurhash({
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const imageData = new ImageData(pixels, width, height);
|
const imageData = new ImageData(pixels, width, height);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Blurhash decoding failure', { err, hash });
|
console.error('Blurhash decoding failure', { err, hash });
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface MissingIndicatorProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MissingIndicator = ({ nested = false }: MissingIndicatorProps): JSX.Element => (
|
const MissingIndicator = ({ nested = false }: MissingIndicatorProps): JSX.Element => (
|
||||||
<Card variant={nested ? null : 'rounded'} size='lg'>
|
<Card variant={nested ? undefined : 'rounded'} size='lg'>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Stack space={2}>
|
<Stack space={2}>
|
||||||
<Text weight='medium' align='center' size='lg'>
|
<Text weight='medium' align='center' size='lg'>
|
||||||
|
|
|
@ -25,9 +25,9 @@ const PullToRefresh = ({ children, onRefresh, ...rest }: IPullToRefresh) => {
|
||||||
return (
|
return (
|
||||||
<PTRComponent
|
<PTRComponent
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
pullingContent={null}
|
pullingContent={<></>}
|
||||||
// `undefined` will fallback to the default, while `null` will render nothing
|
// `undefined` will fallback to the default, while `<></>` will render nothing
|
||||||
refreshingContent={onRefresh ? <Spinner size={30} withText={false} /> : null}
|
refreshingContent={onRefresh ? <Spinner size={30} withText={false} /> : <></>}
|
||||||
pullDownThreshold={67}
|
pullDownThreshold={67}
|
||||||
maxPullDownDistance={95}
|
maxPullDownDistance={95}
|
||||||
resistance={2}
|
resistance={2}
|
||||||
|
|
|
@ -21,9 +21,7 @@ const Avatar = (props: IAvatar) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StillImage
|
<StillImage
|
||||||
className={classNames('rounded-full', {
|
className={classNames('rounded-full', className)}
|
||||||
[className]: typeof className !== 'undefined',
|
|
||||||
})}
|
|
||||||
style={style}
|
style={style}
|
||||||
src={src}
|
src={src}
|
||||||
alt='Avatar'
|
alt='Avatar'
|
||||||
|
|
|
@ -28,8 +28,7 @@ const Card: React.FC<ICard> = React.forwardRef(({ children, variant, size = 'md'
|
||||||
'space-y-4': true,
|
'space-y-4': true,
|
||||||
'bg-white dark:bg-slate-800 sm:shadow-lg dark:sm:shadow-inset overflow-hidden': variant === 'rounded',
|
'bg-white dark:bg-slate-800 sm:shadow-lg dark:sm:shadow-inset overflow-hidden': variant === 'rounded',
|
||||||
[sizes[size]]: true,
|
[sizes[size]]: true,
|
||||||
[className]: typeof className !== 'undefined',
|
}, className)}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,12 +39,14 @@ const HStack: React.FC<IHStack> = (props) => {
|
||||||
<div
|
<div
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
className={classNames('flex', {
|
className={classNames('flex', {
|
||||||
|
// @ts-ignore
|
||||||
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
|
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
|
||||||
|
// @ts-ignore
|
||||||
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
|
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
|
||||||
|
// @ts-ignore
|
||||||
[spaces[space]]: typeof space !== 'undefined',
|
[spaces[space]]: typeof space !== 'undefined',
|
||||||
[className]: typeof className !== 'undefined',
|
|
||||||
'flex-grow': grow,
|
'flex-grow': grow,
|
||||||
})}
|
}, className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,8 +25,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
|
||||||
type='button'
|
type='button'
|
||||||
className={classNames('flex items-center space-x-2 p-1 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 dark:ring-offset-0 focus:ring-primary-500', {
|
className={classNames('flex items-center space-x-2 p-1 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 dark:ring-offset-0 focus:ring-primary-500', {
|
||||||
'bg-white dark:bg-transparent': !transparent,
|
'bg-white dark:bg-transparent': !transparent,
|
||||||
[className]: typeof className !== 'undefined',
|
}, className)}
|
||||||
})}
|
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
>
|
>
|
||||||
<InlineSVG src={src} className={iconClassName} />
|
<InlineSVG src={src} className={iconClassName} />
|
||||||
|
|
|
@ -54,8 +54,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||||
true,
|
true,
|
||||||
'pr-7': isPassword,
|
'pr-7': isPassword,
|
||||||
'pl-8': typeof icon !== 'undefined',
|
'pl-8': typeof icon !== 'undefined',
|
||||||
[className]: typeof className !== 'undefined',
|
}, className)}
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isPassword ? (
|
{isPassword ? (
|
||||||
|
|
|
@ -35,11 +35,13 @@ const Stack: React.FC<IStack> = (props) => {
|
||||||
<div
|
<div
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
className={classNames('flex flex-col', {
|
className={classNames('flex flex-col', {
|
||||||
|
// @ts-ignore
|
||||||
[spaces[space]]: typeof space !== 'undefined',
|
[spaces[space]]: typeof space !== 'undefined',
|
||||||
|
// @ts-ignore
|
||||||
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
|
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
|
||||||
|
// @ts-ignore
|
||||||
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
|
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
|
||||||
[className]: typeof className !== 'undefined',
|
}, className)}
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,13 +24,21 @@ const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
|
||||||
const ref = React.useRef();
|
const ref = React.useRef();
|
||||||
const rect = useRect(ref);
|
const rect = useRect(ref);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const top: number = (activeRect && activeRect.bottom) - (rect && rect.top);
|
const top: number = (activeRect && activeRect.bottom) - (rect && rect.top);
|
||||||
|
// @ts-ignore
|
||||||
const width: number = activeRect && activeRect.width - HORIZONTAL_PADDING * 2;
|
const width: number = activeRect && activeRect.width - HORIZONTAL_PADDING * 2;
|
||||||
|
// @ts-ignore
|
||||||
const left: number = (activeRect && activeRect.left) - (rect && rect.left) + HORIZONTAL_PADDING;
|
const left: number = (activeRect && activeRect.left) - (rect && rect.left) + HORIZONTAL_PADDING;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<AnimatedContext.Provider value={setActiveRect}>
|
<AnimatedContext.Provider value={setActiveRect}>
|
||||||
<ReachTabs {...rest} ref={ref}>
|
<ReachTabs
|
||||||
|
{...rest}
|
||||||
|
// @ts-ignore
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className='w-full h-[3px] bg-primary-200 absolute'
|
className='w-full h-[3px] bg-primary-200 absolute'
|
||||||
style={{ top }}
|
style={{ top }}
|
||||||
|
@ -70,11 +78,13 @@ const AnimatedTab: React.FC<IAnimatedTab> = ({ index, ...props }) => {
|
||||||
// callup to set styles whenever we're active
|
// callup to set styles whenever we're active
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
|
// @ts-ignore
|
||||||
setActiveRect(rect);
|
setActiveRect(rect);
|
||||||
}
|
}
|
||||||
}, [isSelected, rect, setActiveRect]);
|
}, [isSelected, rect, setActiveRect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<ReachTab ref={ref} {...props} />
|
<ReachTab ref={ref} {...props} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -115,6 +125,7 @@ const Tabs = ({ items, activeItem }: ITabs) => {
|
||||||
key={name}
|
key={name}
|
||||||
as='button'
|
as='button'
|
||||||
role='button'
|
role='button'
|
||||||
|
// @ts-ignore
|
||||||
title={title}
|
title={title}
|
||||||
index={idx}
|
index={idx}
|
||||||
>
|
>
|
||||||
|
|
|
@ -83,11 +83,13 @@ const Text: React.FC<IText> = React.forwardRef(
|
||||||
|
|
||||||
const Comp: React.ElementType = tag;
|
const Comp: React.ElementType = tag;
|
||||||
|
|
||||||
|
const alignmentClass = typeof align === 'string' ? alignments[align] : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={tag === 'abbr' ? { textDecoration: 'underline dotted' } : null}
|
style={tag === 'abbr' ? { textDecoration: 'underline dotted' } : undefined}
|
||||||
className={classNames({
|
className={classNames({
|
||||||
'cursor-default': tag === 'abbr',
|
'cursor-default': tag === 'abbr',
|
||||||
truncate: truncate,
|
truncate: truncate,
|
||||||
|
@ -96,9 +98,8 @@ const Text: React.FC<IText> = React.forwardRef(
|
||||||
[weights[weight]]: true,
|
[weights[weight]]: true,
|
||||||
[trackingSizes[tracking]]: true,
|
[trackingSizes[tracking]]: true,
|
||||||
[families[family]]: true,
|
[families[family]]: true,
|
||||||
[alignments[align]]: typeof align !== 'undefined',
|
[alignmentClass]: typeof align !== 'undefined',
|
||||||
[className]: typeof className !== 'undefined',
|
}, className)}
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -90,7 +90,7 @@ const Search = (props: ISearch) => {
|
||||||
|
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
} else if (event.key === 'Escape') {
|
} else if (event.key === 'Escape') {
|
||||||
document.querySelector('.ui').parentElement.focus();
|
document.querySelector('.ui')?.parentElement?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ interface IProfileDropdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IMenuItem = {
|
type IMenuItem = {
|
||||||
text: string | React.ReactElement,
|
text: string | React.ReactElement | null,
|
||||||
to?: string,
|
to?: string,
|
||||||
icon?: string,
|
icon?: string,
|
||||||
action?: (event: React.MouseEvent) => void
|
action?: (event: React.MouseEvent) => void
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
import { makeGetAccount } from 'soapbox/selectors';
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
import type Account from 'soapbox/types/entities/account';
|
import type { Account } from 'soapbox/types/entities';
|
||||||
|
|
||||||
// FIXME: There is no reason this selector shouldn't be global accross the whole app
|
// FIXME: There is no reason this selector shouldn't be global accross the whole app
|
||||||
// FIXME: getAccount() has the wrong type??
|
// FIXME: getAccount() has the wrong type??
|
||||||
|
|
|
@ -17,17 +17,19 @@ import { acctFull } from 'soapbox/utils/accounts';
|
||||||
import { unescapeHTML } from 'soapbox/utils/html';
|
import { unescapeHTML } from 'soapbox/utils/html';
|
||||||
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
|
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
|
import type { Emoji, Field, EmbeddedEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
// https://docs.joinmastodon.org/entities/account/
|
// https://docs.joinmastodon.org/entities/account/
|
||||||
export const AccountRecord = ImmutableRecord({
|
export const AccountRecord = ImmutableRecord({
|
||||||
acct: '',
|
acct: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
avatar_static: '',
|
avatar_static: '',
|
||||||
birthday: undefined,
|
birthday: undefined as Date | undefined,
|
||||||
bot: false,
|
bot: false,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
display_name: '',
|
display_name: '',
|
||||||
emojis: ImmutableList(),
|
emojis: ImmutableList<Emoji>(),
|
||||||
fields: ImmutableList(),
|
fields: ImmutableList<Field>(),
|
||||||
followers_count: 0,
|
followers_count: 0,
|
||||||
following_count: 0,
|
following_count: 0,
|
||||||
fqn: '',
|
fqn: '',
|
||||||
|
@ -37,10 +39,10 @@ export const AccountRecord = ImmutableRecord({
|
||||||
last_status_at: new Date(),
|
last_status_at: new Date(),
|
||||||
location: '',
|
location: '',
|
||||||
locked: false,
|
locked: false,
|
||||||
moved: null,
|
moved: null as EmbeddedEntity<any> | null,
|
||||||
note: '',
|
note: '',
|
||||||
pleroma: ImmutableMap(),
|
pleroma: ImmutableMap<string, any>(),
|
||||||
source: ImmutableMap(),
|
source: ImmutableMap<string, any>(),
|
||||||
statuses_count: 0,
|
statuses_count: 0,
|
||||||
uri: '',
|
uri: '',
|
||||||
url: '',
|
url: '',
|
||||||
|
@ -52,8 +54,8 @@ export const AccountRecord = ImmutableRecord({
|
||||||
display_name_html: '',
|
display_name_html: '',
|
||||||
note_emojified: '',
|
note_emojified: '',
|
||||||
note_plain: '',
|
note_plain: '',
|
||||||
patron: ImmutableMap(),
|
patron: ImmutableMap<string, any>(),
|
||||||
relationship: ImmutableList(),
|
relationship: ImmutableList<ImmutableMap<string, any>>(),
|
||||||
should_refetch: false,
|
should_refetch: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,7 +63,7 @@ export const AccountRecord = ImmutableRecord({
|
||||||
export const FieldRecord = ImmutableRecord({
|
export const FieldRecord = ImmutableRecord({
|
||||||
name: '',
|
name: '',
|
||||||
value: '',
|
value: '',
|
||||||
verified_at: null,
|
verified_at: null as Date | null,
|
||||||
|
|
||||||
// Internal fields
|
// Internal fields
|
||||||
name_emojified: '',
|
name_emojified: '',
|
||||||
|
|
|
@ -82,7 +82,7 @@ const pleromaToMastodonConfig = (instance: ImmutableMap<string, any>) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the software's default attachment limit
|
// Get the software's default attachment limit
|
||||||
const getAttachmentLimit = (software: string) => software === PLEROMA ? Infinity : 4;
|
const getAttachmentLimit = (software: string | null) => software === PLEROMA ? Infinity : 4;
|
||||||
|
|
||||||
// Normalize version
|
// Normalize version
|
||||||
const normalizeVersion = (instance: ImmutableMap<string, any>) => {
|
const normalizeVersion = (instance: ImmutableMap<string, any>) => {
|
||||||
|
|
|
@ -15,17 +15,19 @@ import emojify from 'soapbox/features/emoji/emoji';
|
||||||
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
|
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
|
||||||
import { makeEmojiMap } from 'soapbox/utils/normalizers';
|
import { makeEmojiMap } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
|
import type { Emoji, PollOption } from 'soapbox/types/entities';
|
||||||
|
|
||||||
// https://docs.joinmastodon.org/entities/poll/
|
// https://docs.joinmastodon.org/entities/poll/
|
||||||
export const PollRecord = ImmutableRecord({
|
export const PollRecord = ImmutableRecord({
|
||||||
emojis: ImmutableList(),
|
emojis: ImmutableList<Emoji>(),
|
||||||
expired: false,
|
expired: false,
|
||||||
expires_at: new Date(),
|
expires_at: new Date(),
|
||||||
id: '',
|
id: '',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
options: ImmutableList(),
|
options: ImmutableList<PollOption>(),
|
||||||
voters_count: 0,
|
voters_count: 0,
|
||||||
votes_count: 0,
|
votes_count: 0,
|
||||||
own_votes: null,
|
own_votes: null as ImmutableList<number> | null,
|
||||||
voted: false,
|
voted: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,38 +16,42 @@ import { normalizeEmoji } from 'soapbox/normalizers/emoji';
|
||||||
import { normalizeMention } from 'soapbox/normalizers/mention';
|
import { normalizeMention } from 'soapbox/normalizers/mention';
|
||||||
import { normalizePoll } from 'soapbox/normalizers/poll';
|
import { normalizePoll } from 'soapbox/normalizers/poll';
|
||||||
|
|
||||||
|
import type { Account, Attachment, Card, Emoji, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct';
|
||||||
|
|
||||||
// https://docs.joinmastodon.org/entities/status/
|
// https://docs.joinmastodon.org/entities/status/
|
||||||
export const StatusRecord = ImmutableRecord({
|
export const StatusRecord = ImmutableRecord({
|
||||||
account: null,
|
account: null as EmbeddedEntity<Account>,
|
||||||
application: null,
|
application: null as ImmutableMap<string, any> | null,
|
||||||
bookmarked: false,
|
bookmarked: false,
|
||||||
card: null,
|
card: null as EmbeddedEntity<Card>,
|
||||||
content: '',
|
content: '',
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
emojis: ImmutableList(),
|
emojis: ImmutableList<Emoji>(),
|
||||||
favourited: false,
|
favourited: false,
|
||||||
favourites_count: 0,
|
favourites_count: 0,
|
||||||
in_reply_to_account_id: null,
|
in_reply_to_account_id: null as string | null,
|
||||||
in_reply_to_id: null,
|
in_reply_to_id: null as string | null,
|
||||||
id: '',
|
id: '',
|
||||||
language: null,
|
language: null as string | null,
|
||||||
media_attachments: ImmutableList(),
|
media_attachments: ImmutableList<Attachment>(),
|
||||||
mentions: ImmutableList(),
|
mentions: ImmutableList<Mention>(),
|
||||||
muted: false,
|
muted: false,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
pleroma: ImmutableMap(),
|
pleroma: ImmutableMap<string, any>(),
|
||||||
poll: null,
|
poll: null as EmbeddedEntity<Poll>,
|
||||||
quote: null,
|
quote: null as EmbeddedEntity<any>,
|
||||||
reblog: null,
|
reblog: null as EmbeddedEntity<any>,
|
||||||
reblogged: false,
|
reblogged: false,
|
||||||
reblogs_count: 0,
|
reblogs_count: 0,
|
||||||
replies_count: 0,
|
replies_count: 0,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
spoiler_text: '',
|
spoiler_text: '',
|
||||||
tags: ImmutableList(),
|
tags: ImmutableList<ImmutableMap<string, any>>(),
|
||||||
uri: '',
|
uri: '',
|
||||||
url: '',
|
url: '',
|
||||||
visibility: 'public',
|
visibility: 'public' as StatusVisibility,
|
||||||
|
|
||||||
// Internal fields
|
// Internal fields
|
||||||
contentHtml: '',
|
contentHtml: '',
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats';
|
import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats';
|
||||||
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
||||||
import { normalizeAccount } from 'soapbox/normalizers/account';
|
import { normalizeAccount } from 'soapbox/normalizers/account';
|
||||||
|
import { normalizeId } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ACCOUNT_IMPORT,
|
ACCOUNT_IMPORT,
|
||||||
|
@ -50,7 +51,7 @@ const initialState: State = ImmutableMap();
|
||||||
|
|
||||||
const minifyAccount = (account: AccountRecord): AccountRecord => {
|
const minifyAccount = (account: AccountRecord): AccountRecord => {
|
||||||
return account.mergeWith((o, n) => n || o, {
|
return account.mergeWith((o, n) => n || o, {
|
||||||
moved: account.getIn(['moved', 'id']),
|
moved: normalizeId(account.getIn(['moved', 'id'])),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,8 +202,8 @@ const importAdminUser = (state: State, adminUser: ImmutableMap<string, any>): St
|
||||||
|
|
||||||
const importAdminUsers = (state: State, adminUsers: Array<Record<string, any>>): State => {
|
const importAdminUsers = (state: State, adminUsers: Array<Record<string, any>>): State => {
|
||||||
return state.withMutations((state: State) => {
|
return state.withMutations((state: State) => {
|
||||||
fromJS(adminUsers).forEach(adminUser => {
|
adminUsers.forEach(adminUser => {
|
||||||
importAdminUser(state, ImmutableMap(adminUser));
|
importAdminUser(state, ImmutableMap(fromJS(adminUser)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ import emojify from 'soapbox/features/emoji/emoji';
|
||||||
import { normalizeStatus } from 'soapbox/normalizers';
|
import { normalizeStatus } from 'soapbox/normalizers';
|
||||||
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts';
|
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts';
|
||||||
import { stripCompatibilityFeatures, unescapeHTML } from 'soapbox/utils/html';
|
import { stripCompatibilityFeatures, unescapeHTML } from 'soapbox/utils/html';
|
||||||
import { makeEmojiMap } from 'soapbox/utils/normalizers';
|
import { makeEmojiMap, normalizeId } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EMOJI_REACT_REQUEST,
|
EMOJI_REACT_REQUEST,
|
||||||
|
@ -42,16 +42,20 @@ type State = ImmutableMap<string, StatusRecord>;
|
||||||
|
|
||||||
const minifyStatus = (status: StatusRecord): StatusRecord => {
|
const minifyStatus = (status: StatusRecord): StatusRecord => {
|
||||||
return status.mergeWith((o, n) => n || o, {
|
return status.mergeWith((o, n) => n || o, {
|
||||||
account: status.getIn(['account', 'id']),
|
account: normalizeId(status.getIn(['account', 'id'])),
|
||||||
reblog: status.getIn(['reblog', 'id']),
|
reblog: normalizeId(status.getIn(['reblog', 'id'])),
|
||||||
poll: status.getIn(['poll', 'id']),
|
poll: normalizeId(status.getIn(['poll', 'id'])),
|
||||||
quote: status.getIn(['quote', 'id']),
|
quote: normalizeId(status.getIn(['quote', 'id'])),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gets titles of poll options from status
|
// Gets titles of poll options from status
|
||||||
const getPollOptionTitles = (status: StatusRecord): Array<string> => {
|
const getPollOptionTitles = ({ poll }: StatusRecord): ImmutableList<string> => {
|
||||||
return status.poll?.options.map(({ title }: { title: string }) => title);
|
if (poll && typeof poll === 'object') {
|
||||||
|
return poll.options.map(({ title }) => title);
|
||||||
|
} else {
|
||||||
|
return ImmutableList();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Creates search text from the status
|
// Creates search text from the status
|
||||||
|
@ -63,14 +67,14 @@ const buildSearchContent = (status: StatusRecord): string => {
|
||||||
status.content,
|
status.content,
|
||||||
]).concat(pollOptionTitles);
|
]).concat(pollOptionTitles);
|
||||||
|
|
||||||
return unescapeHTML(fields.join('\n\n'));
|
return unescapeHTML(fields.join('\n\n')) || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only calculate these values when status first encountered
|
// Only calculate these values when status first encountered
|
||||||
// Otherwise keep the ones already in the reducer
|
// Otherwise keep the ones already in the reducer
|
||||||
export const calculateStatus = (
|
export const calculateStatus = (
|
||||||
status: StatusRecord,
|
status: StatusRecord,
|
||||||
oldStatus: StatusRecord,
|
oldStatus?: StatusRecord,
|
||||||
expandSpoilers: boolean = false,
|
expandSpoilers: boolean = false,
|
||||||
): StatusRecord => {
|
): StatusRecord => {
|
||||||
if (oldStatus) {
|
if (oldStatus) {
|
||||||
|
@ -86,7 +90,7 @@ export const calculateStatus = (
|
||||||
const emojiMap = makeEmojiMap(status.emojis);
|
const emojiMap = makeEmojiMap(status.emojis);
|
||||||
|
|
||||||
return status.merge({
|
return status.merge({
|
||||||
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || undefined,
|
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '',
|
||||||
contentHtml: stripCompatibilityFeatures(emojify(status.content, emojiMap)),
|
contentHtml: stripCompatibilityFeatures(emojify(status.content, emojiMap)),
|
||||||
spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap),
|
spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap),
|
||||||
hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive,
|
hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive,
|
||||||
|
@ -100,7 +104,7 @@ const isQuote = (status: StatusRecord) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Preserve quote if an existing status already has it
|
// Preserve quote if an existing status already has it
|
||||||
const fixQuote = (status: StatusRecord, oldStatus: StatusRecord): StatusRecord => {
|
const fixQuote = (status: StatusRecord, oldStatus?: StatusRecord): StatusRecord => {
|
||||||
if (oldStatus && !status.quote && isQuote(status)) {
|
if (oldStatus && !status.quote && isQuote(status)) {
|
||||||
return status
|
return status
|
||||||
.set('quote', oldStatus.quote)
|
.set('quote', oldStatus.quote)
|
||||||
|
@ -111,7 +115,7 @@ const fixQuote = (status: StatusRecord, oldStatus: StatusRecord): StatusRecord =
|
||||||
};
|
};
|
||||||
|
|
||||||
const fixStatus = (state: State, status: APIEntity, expandSpoilers: boolean): StatusRecord => {
|
const fixStatus = (state: State, status: APIEntity, expandSpoilers: boolean): StatusRecord => {
|
||||||
const oldStatus: StatusRecord = state.get(status.id);
|
const oldStatus = state.get(status.id);
|
||||||
|
|
||||||
return normalizeStatus(status).withMutations(status => {
|
return normalizeStatus(status).withMutations(status => {
|
||||||
fixQuote(status, oldStatus);
|
fixQuote(status, oldStatus);
|
||||||
|
@ -154,6 +158,25 @@ const deletePendingStatus = (state: State, { in_reply_to_id }: APIEntity) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Simulate favourite/unfavourite of status for optimistic interactions */
|
||||||
|
const simulateFavourite = (
|
||||||
|
state: State,
|
||||||
|
statusId: string,
|
||||||
|
favourited: boolean,
|
||||||
|
): State => {
|
||||||
|
const status = state.get(statusId);
|
||||||
|
if (!status) return state;
|
||||||
|
|
||||||
|
const delta = favourited ? +1 : -1;
|
||||||
|
|
||||||
|
const updatedStatus = status.merge({
|
||||||
|
favourited,
|
||||||
|
favourites_count: Math.max(0, status.favourites_count + delta),
|
||||||
|
});
|
||||||
|
|
||||||
|
return state.set(statusId, updatedStatus);
|
||||||
|
};
|
||||||
|
|
||||||
const initialState: State = ImmutableMap();
|
const initialState: State = ImmutableMap();
|
||||||
|
|
||||||
export default function statuses(state = initialState, action: AnyAction): State {
|
export default function statuses(state = initialState, action: AnyAction): State {
|
||||||
|
@ -167,15 +190,9 @@ export default function statuses(state = initialState, action: AnyAction): State
|
||||||
case STATUS_CREATE_FAIL:
|
case STATUS_CREATE_FAIL:
|
||||||
return deletePendingStatus(state, action.params);
|
return deletePendingStatus(state, action.params);
|
||||||
case FAVOURITE_REQUEST:
|
case FAVOURITE_REQUEST:
|
||||||
return state.update(action.status.get('id'), status =>
|
return simulateFavourite(state, action.status.id, true);
|
||||||
status
|
|
||||||
.set('favourited', true)
|
|
||||||
.update('favourites_count', count => count + 1));
|
|
||||||
case UNFAVOURITE_REQUEST:
|
case UNFAVOURITE_REQUEST:
|
||||||
return state.update(action.status.get('id'), status =>
|
return simulateFavourite(state, action.status.id, false);
|
||||||
status
|
|
||||||
.set('favourited', false)
|
|
||||||
.update('favourites_count', count => Math.max(0, count - 1)));
|
|
||||||
case EMOJI_REACT_REQUEST:
|
case EMOJI_REACT_REQUEST:
|
||||||
return state
|
return state
|
||||||
.updateIn(
|
.updateIn(
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
AccountRecord,
|
||||||
|
AttachmentRecord,
|
||||||
|
CardRecord,
|
||||||
|
EmojiRecord,
|
||||||
|
FieldRecord,
|
||||||
|
InstanceRecord,
|
||||||
|
MentionRecord,
|
||||||
|
NotificationRecord,
|
||||||
|
PollRecord,
|
||||||
|
PollOptionRecord,
|
||||||
|
StatusRecord,
|
||||||
|
} from 'soapbox/normalizers';
|
||||||
|
|
||||||
|
import type { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
|
type Account = ReturnType<typeof AccountRecord>;
|
||||||
|
type Attachment = ReturnType<typeof AttachmentRecord>;
|
||||||
|
type Card = ReturnType<typeof CardRecord>;
|
||||||
|
type Emoji = ReturnType<typeof EmojiRecord>;
|
||||||
|
type Field = ReturnType<typeof FieldRecord>;
|
||||||
|
type Instance = ReturnType<typeof InstanceRecord>;
|
||||||
|
type Mention = ReturnType<typeof MentionRecord>;
|
||||||
|
type Notification = ReturnType<typeof NotificationRecord>;
|
||||||
|
type Poll = ReturnType<typeof PollRecord>;
|
||||||
|
type PollOption = ReturnType<typeof PollOptionRecord>;
|
||||||
|
type Status = ReturnType<typeof StatusRecord>;
|
||||||
|
|
||||||
|
// Utility types
|
||||||
|
type EmbeddedEntity<T extends object> = null | string | ReturnType<ImmutableRecord.Factory<T>>;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Account,
|
||||||
|
Attachment,
|
||||||
|
Card,
|
||||||
|
Emoji,
|
||||||
|
Field,
|
||||||
|
Instance,
|
||||||
|
Mention,
|
||||||
|
Notification,
|
||||||
|
Poll,
|
||||||
|
PollOption,
|
||||||
|
Status,
|
||||||
|
|
||||||
|
// Utility types
|
||||||
|
EmbeddedEntity,
|
||||||
|
};
|
|
@ -1,10 +0,0 @@
|
||||||
/**
|
|
||||||
* Account entity.
|
|
||||||
* https://docs.joinmastodon.org/entities/account/
|
|
||||||
**/
|
|
||||||
|
|
||||||
import { AccountRecord } from 'soapbox/normalizers';
|
|
||||||
|
|
||||||
type Account = ReturnType<typeof AccountRecord>
|
|
||||||
|
|
||||||
export default Account;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as Account } from './account';
|
|
||||||
export { default as Status } from './status';
|
|
|
@ -1,10 +0,0 @@
|
||||||
/**
|
|
||||||
* Status entity.
|
|
||||||
* https://docs.joinmastodon.org/entities/status/
|
|
||||||
**/
|
|
||||||
|
|
||||||
import { StatusRecord } from 'soapbox/normalizers';
|
|
||||||
|
|
||||||
type Status = ReturnType<typeof StatusRecord>
|
|
||||||
|
|
||||||
export default Status;
|
|
|
@ -1,7 +0,0 @@
|
||||||
// Use new value only if old value is undefined
|
|
||||||
export const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal;
|
|
||||||
|
|
||||||
export const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
|
|
||||||
obj[`:${emoji.shortcode}:`] = emoji;
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Use new value only if old value is undefined
|
||||||
|
export const mergeDefined = (oldVal: any, newVal: any) => oldVal === undefined ? newVal : oldVal;
|
||||||
|
|
||||||
|
export const makeEmojiMap = (emojis: any) => emojis.reduce((obj: any, emoji: any) => {
|
||||||
|
obj[`:${emoji.shortcode}:`] = emoji;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
/** Normalize entity ID */
|
||||||
|
export const normalizeId = (id: any): string | null => {
|
||||||
|
return typeof id === 'string' ? id : null;
|
||||||
|
};
|
|
@ -2,14 +2,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "app/",
|
"baseUrl": "app/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"alwaysStrict": true,
|
"strict": true,
|
||||||
"strictNullChecks": false,
|
|
||||||
"strictBindCallApply": true,
|
|
||||||
"strictFunctionTypes": true,
|
|
||||||
"strictPropertyInitialization": false,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noImplicitThis": true,
|
|
||||||
"useUnknownInCatchVariables": true,
|
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
|
Loading…
Reference in New Issue