Put Typescript in "strict" mode, fix or ignore errors

merge-requests/1145/head
Alex Gleason 2022-03-24 14:27:27 -05:00
rodzic 84d7d2ee38
commit 2940a3ff4d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
28 zmienionych plików z 177 dodań i 114 usunięć

Wyświetl plik

@ -54,6 +54,7 @@ const Account = ({
}: IAccount) => {
const overflowRef = React.useRef<HTMLDivElement>(null);
const actionRef = React.useRef<HTMLDivElement>(null);
// @ts-ignore
const isOnScreen = useOnScreen(overflowRef);
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 handleAction = () => {
// @ts-ignore
onActionClick(account);
};
@ -96,7 +98,7 @@ const Account = ({
React.useEffect(() => {
if (isOnScreen) {
const style: React.CSSProperties = {};
const actionWidth = actionRef.current?.clientWidth;
const actionWidth = actionRef.current?.clientWidth || 0;
if (overflowRef.current) {
style.maxWidth = overflowRef.current.clientWidth - 30 - avatarSize - actionWidth;

Wyświetl plik

@ -46,6 +46,7 @@ function Blurhash({
const ctx = canvas.getContext('2d');
const imageData = new ImageData(pixels, width, height);
// @ts-ignore
ctx.putImageData(imageData, 0, 0);
} catch (err) {
console.error('Blurhash decoding failure', { err, hash });

Wyświetl plik

@ -8,7 +8,7 @@ interface MissingIndicatorProps {
}
const MissingIndicator = ({ nested = false }: MissingIndicatorProps): JSX.Element => (
<Card variant={nested ? null : 'rounded'} size='lg'>
<Card variant={nested ? undefined : 'rounded'} size='lg'>
<CardBody>
<Stack space={2}>
<Text weight='medium' align='center' size='lg'>

Wyświetl plik

@ -25,9 +25,9 @@ const PullToRefresh = ({ children, onRefresh, ...rest }: IPullToRefresh) => {
return (
<PTRComponent
onRefresh={handleRefresh}
pullingContent={null}
// `undefined` will fallback to the default, while `null` will render nothing
refreshingContent={onRefresh ? <Spinner size={30} withText={false} /> : null}
pullingContent={<></>}
// `undefined` will fallback to the default, while `<></>` will render nothing
refreshingContent={onRefresh ? <Spinner size={30} withText={false} /> : <></>}
pullDownThreshold={67}
maxPullDownDistance={95}
resistance={2}

Wyświetl plik

@ -21,9 +21,7 @@ const Avatar = (props: IAvatar) => {
return (
<StillImage
className={classNames('rounded-full', {
[className]: typeof className !== 'undefined',
})}
className={classNames('rounded-full', className)}
style={style}
src={src}
alt='Avatar'

Wyświetl plik

@ -28,8 +28,7 @@ const Card: React.FC<ICard> = React.forwardRef(({ children, variant, size = 'md'
'space-y-4': true,
'bg-white dark:bg-slate-800 sm:shadow-lg dark:sm:shadow-inset overflow-hidden': variant === 'rounded',
[sizes[size]]: true,
[className]: typeof className !== 'undefined',
})}
}, className)}
>
{children}
</div>

Wyświetl plik

@ -39,12 +39,14 @@ const HStack: React.FC<IHStack> = (props) => {
<div
{...filteredProps}
className={classNames('flex', {
// @ts-ignore
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
// @ts-ignore
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
[className]: typeof className !== 'undefined',
'flex-grow': grow,
})}
}, className)}
/>
);
};

Wyświetl plik

@ -25,8 +25,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
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', {
'bg-white dark:bg-transparent': !transparent,
[className]: typeof className !== 'undefined',
})}
}, className)}
{...filteredProps}
>
<InlineSVG src={src} className={iconClassName} />

Wyświetl plik

@ -54,8 +54,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
true,
'pr-7': isPassword,
'pl-8': typeof icon !== 'undefined',
[className]: typeof className !== 'undefined',
})}
}, className)}
/>
{isPassword ? (

Wyświetl plik

@ -35,11 +35,13 @@ const Stack: React.FC<IStack> = (props) => {
<div
{...filteredProps}
className={classNames('flex flex-col', {
// @ts-ignore
[spaces[space]]: typeof space !== 'undefined',
// @ts-ignore
[alignItemsOptions[alignItems]]: typeof alignItems !== 'undefined',
// @ts-ignore
[justifyContentOptions[justifyContent]]: typeof justifyContent !== 'undefined',
[className]: typeof className !== 'undefined',
})}
}, className)}
/>
);
};

Wyświetl plik

@ -24,13 +24,21 @@ const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
const ref = React.useRef();
const rect = useRect(ref);
// @ts-ignore
const top: number = (activeRect && activeRect.bottom) - (rect && rect.top);
// @ts-ignore
const width: number = activeRect && activeRect.width - HORIZONTAL_PADDING * 2;
// @ts-ignore
const left: number = (activeRect && activeRect.left) - (rect && rect.left) + HORIZONTAL_PADDING;
return (
// @ts-ignore
<AnimatedContext.Provider value={setActiveRect}>
<ReachTabs {...rest} ref={ref}>
<ReachTabs
{...rest}
// @ts-ignore
ref={ref}
>
<div
className='w-full h-[3px] bg-primary-200 absolute'
style={{ top }}
@ -70,11 +78,13 @@ const AnimatedTab: React.FC<IAnimatedTab> = ({ index, ...props }) => {
// callup to set styles whenever we're active
React.useLayoutEffect(() => {
if (isSelected) {
// @ts-ignore
setActiveRect(rect);
}
}, [isSelected, rect, setActiveRect]);
return (
// @ts-ignore
<ReachTab ref={ref} {...props} />
);
};
@ -115,6 +125,7 @@ const Tabs = ({ items, activeItem }: ITabs) => {
key={name}
as='button'
role='button'
// @ts-ignore
title={title}
index={idx}
>

Wyświetl plik

@ -83,11 +83,13 @@ const Text: React.FC<IText> = React.forwardRef(
const Comp: React.ElementType = tag;
const alignmentClass = typeof align === 'string' ? alignments[align] : '';
return (
<Comp
{...filteredProps}
ref={ref}
style={tag === 'abbr' ? { textDecoration: 'underline dotted' } : null}
style={tag === 'abbr' ? { textDecoration: 'underline dotted' } : undefined}
className={classNames({
'cursor-default': tag === 'abbr',
truncate: truncate,
@ -96,9 +98,8 @@ const Text: React.FC<IText> = React.forwardRef(
[weights[weight]]: true,
[trackingSizes[tracking]]: true,
[families[family]]: true,
[alignments[align]]: typeof align !== 'undefined',
[className]: typeof className !== 'undefined',
})}
[alignmentClass]: typeof align !== 'undefined',
}, className)}
/>
);
},

Wyświetl plik

@ -90,7 +90,7 @@ const Search = (props: ISearch) => {
handleSubmit();
} else if (event.key === 'Escape') {
document.querySelector('.ui').parentElement.focus();
document.querySelector('.ui')?.parentElement?.focus();
}
};

Wyświetl plik

@ -25,7 +25,7 @@ interface IProfileDropdown {
}
type IMenuItem = {
text: string | React.ReactElement,
text: string | React.ReactElement | null,
to?: string,
icon?: string,
action?: (event: React.MouseEvent) => void

Wyświetl plik

@ -1,7 +1,7 @@
import { useAppSelector } from 'soapbox/hooks';
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: getAccount() has the wrong type??

Wyświetl plik

@ -17,17 +17,19 @@ import { acctFull } from 'soapbox/utils/accounts';
import { unescapeHTML } from 'soapbox/utils/html';
import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
import type { Emoji, Field, EmbeddedEntity } from 'soapbox/types/entities';
// https://docs.joinmastodon.org/entities/account/
export const AccountRecord = ImmutableRecord({
acct: '',
avatar: '',
avatar_static: '',
birthday: undefined,
birthday: undefined as Date | undefined,
bot: false,
created_at: new Date(),
display_name: '',
emojis: ImmutableList(),
fields: ImmutableList(),
emojis: ImmutableList<Emoji>(),
fields: ImmutableList<Field>(),
followers_count: 0,
following_count: 0,
fqn: '',
@ -37,10 +39,10 @@ export const AccountRecord = ImmutableRecord({
last_status_at: new Date(),
location: '',
locked: false,
moved: null,
moved: null as EmbeddedEntity<any> | null,
note: '',
pleroma: ImmutableMap(),
source: ImmutableMap(),
pleroma: ImmutableMap<string, any>(),
source: ImmutableMap<string, any>(),
statuses_count: 0,
uri: '',
url: '',
@ -52,8 +54,8 @@ export const AccountRecord = ImmutableRecord({
display_name_html: '',
note_emojified: '',
note_plain: '',
patron: ImmutableMap(),
relationship: ImmutableList(),
patron: ImmutableMap<string, any>(),
relationship: ImmutableList<ImmutableMap<string, any>>(),
should_refetch: false,
});
@ -61,7 +63,7 @@ export const AccountRecord = ImmutableRecord({
export const FieldRecord = ImmutableRecord({
name: '',
value: '',
verified_at: null,
verified_at: null as Date | null,
// Internal fields
name_emojified: '',

Wyświetl plik

@ -82,7 +82,7 @@ const pleromaToMastodonConfig = (instance: ImmutableMap<string, any>) => {
};
// 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
const normalizeVersion = (instance: ImmutableMap<string, any>) => {

Wyświetl plik

@ -15,17 +15,19 @@ import emojify from 'soapbox/features/emoji/emoji';
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { makeEmojiMap } from 'soapbox/utils/normalizers';
import type { Emoji, PollOption } from 'soapbox/types/entities';
// https://docs.joinmastodon.org/entities/poll/
export const PollRecord = ImmutableRecord({
emojis: ImmutableList(),
emojis: ImmutableList<Emoji>(),
expired: false,
expires_at: new Date(),
id: '',
multiple: false,
options: ImmutableList(),
options: ImmutableList<PollOption>(),
voters_count: 0,
votes_count: 0,
own_votes: null,
own_votes: null as ImmutableList<number> | null,
voted: false,
});

Wyświetl plik

@ -16,38 +16,42 @@ import { normalizeEmoji } from 'soapbox/normalizers/emoji';
import { normalizeMention } from 'soapbox/normalizers/mention';
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/
export const StatusRecord = ImmutableRecord({
account: null,
application: null,
account: null as EmbeddedEntity<Account>,
application: null as ImmutableMap<string, any> | null,
bookmarked: false,
card: null,
card: null as EmbeddedEntity<Card>,
content: '',
created_at: new Date(),
emojis: ImmutableList(),
emojis: ImmutableList<Emoji>(),
favourited: false,
favourites_count: 0,
in_reply_to_account_id: null,
in_reply_to_id: null,
in_reply_to_account_id: null as string | null,
in_reply_to_id: null as string | null,
id: '',
language: null,
media_attachments: ImmutableList(),
mentions: ImmutableList(),
language: null as string | null,
media_attachments: ImmutableList<Attachment>(),
mentions: ImmutableList<Mention>(),
muted: false,
pinned: false,
pleroma: ImmutableMap(),
poll: null,
quote: null,
reblog: null,
pleroma: ImmutableMap<string, any>(),
poll: null as EmbeddedEntity<Poll>,
quote: null as EmbeddedEntity<any>,
reblog: null as EmbeddedEntity<any>,
reblogged: false,
reblogs_count: 0,
replies_count: 0,
sensitive: false,
spoiler_text: '',
tags: ImmutableList(),
tags: ImmutableList<ImmutableMap<string, any>>(),
uri: '',
url: '',
visibility: 'public',
visibility: 'public' as StatusVisibility,
// Internal fields
contentHtml: '',

Wyświetl plik

@ -32,6 +32,7 @@ import {
import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats';
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { normalizeAccount } from 'soapbox/normalizers/account';
import { normalizeId } from 'soapbox/utils/normalizers';
import {
ACCOUNT_IMPORT,
@ -50,7 +51,7 @@ const initialState: State = ImmutableMap();
const minifyAccount = (account: AccountRecord): AccountRecord => {
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 => {
return state.withMutations((state: State) => {
fromJS(adminUsers).forEach(adminUser => {
importAdminUser(state, ImmutableMap(adminUser));
adminUsers.forEach(adminUser => {
importAdminUser(state, ImmutableMap(fromJS(adminUser)));
});
});
};

Wyświetl plik

@ -6,7 +6,7 @@ import emojify from 'soapbox/features/emoji/emoji';
import { normalizeStatus } from 'soapbox/normalizers';
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts';
import { stripCompatibilityFeatures, unescapeHTML } from 'soapbox/utils/html';
import { makeEmojiMap } from 'soapbox/utils/normalizers';
import { makeEmojiMap, normalizeId } from 'soapbox/utils/normalizers';
import {
EMOJI_REACT_REQUEST,
@ -42,16 +42,20 @@ type State = ImmutableMap<string, StatusRecord>;
const minifyStatus = (status: StatusRecord): StatusRecord => {
return status.mergeWith((o, n) => n || o, {
account: status.getIn(['account', 'id']),
reblog: status.getIn(['reblog', 'id']),
poll: status.getIn(['poll', 'id']),
quote: status.getIn(['quote', 'id']),
account: normalizeId(status.getIn(['account', 'id'])),
reblog: normalizeId(status.getIn(['reblog', 'id'])),
poll: normalizeId(status.getIn(['poll', 'id'])),
quote: normalizeId(status.getIn(['quote', 'id'])),
});
};
// Gets titles of poll options from status
const getPollOptionTitles = (status: StatusRecord): Array<string> => {
return status.poll?.options.map(({ title }: { title: string }) => title);
const getPollOptionTitles = ({ poll }: StatusRecord): ImmutableList<string> => {
if (poll && typeof poll === 'object') {
return poll.options.map(({ title }) => title);
} else {
return ImmutableList();
}
};
// Creates search text from the status
@ -63,14 +67,14 @@ const buildSearchContent = (status: StatusRecord): string => {
status.content,
]).concat(pollOptionTitles);
return unescapeHTML(fields.join('\n\n'));
return unescapeHTML(fields.join('\n\n')) || '';
};
// Only calculate these values when status first encountered
// Otherwise keep the ones already in the reducer
export const calculateStatus = (
status: StatusRecord,
oldStatus: StatusRecord,
oldStatus?: StatusRecord,
expandSpoilers: boolean = false,
): StatusRecord => {
if (oldStatus) {
@ -86,7 +90,7 @@ export const calculateStatus = (
const emojiMap = makeEmojiMap(status.emojis);
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)),
spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap),
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
const fixQuote = (status: StatusRecord, oldStatus: StatusRecord): StatusRecord => {
const fixQuote = (status: StatusRecord, oldStatus?: StatusRecord): StatusRecord => {
if (oldStatus && !status.quote && isQuote(status)) {
return status
.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 oldStatus: StatusRecord = state.get(status.id);
const oldStatus = state.get(status.id);
return normalizeStatus(status).withMutations(status => {
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();
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:
return deletePendingStatus(state, action.params);
case FAVOURITE_REQUEST:
return state.update(action.status.get('id'), status =>
status
.set('favourited', true)
.update('favourites_count', count => count + 1));
return simulateFavourite(state, action.status.id, true);
case UNFAVOURITE_REQUEST:
return state.update(action.status.get('id'), status =>
status
.set('favourited', false)
.update('favourites_count', count => Math.max(0, count - 1)));
return simulateFavourite(state, action.status.id, false);
case EMOJI_REACT_REQUEST:
return state
.updateIn(

Wyświetl plik

@ -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,
};

Wyświetl plik

@ -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;

Wyświetl plik

@ -1,2 +0,0 @@
export { default as Account } from './account';
export { default as Status } from './status';

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}, {});

Wyświetl plik

@ -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;
};

Wyświetl plik

@ -2,14 +2,7 @@
"compilerOptions": {
"baseUrl": "app/",
"sourceMap": true,
"alwaysStrict": true,
"strictNullChecks": false,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": false,
"noImplicitAny": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"strict": true,
"module": "es6",
"target": "es5",
"jsx": "react",