[Refactoring] Teact: Introduce TeactNode type for JSX typing

This commit is contained in:
Alexander Zinchuk 2022-05-16 13:34:27 +02:00
parent 183a5fad5b
commit c92a18895e
15 changed files with 45 additions and 40 deletions

View File

@ -1,3 +0,0 @@
declare namespace JSX {
type Element = VirtualElement;
}

View File

@ -1,5 +1,7 @@
import { MouseEvent as ReactMouseEvent } from 'react';
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import React, {
FC, memo, TeactNode, useCallback,
} from '../../lib/teact/teact';
import {
ApiChat, ApiMediaFormat, ApiPhoto, ApiUser, ApiUserStatus,
@ -75,7 +77,7 @@ const Avatar: FC<OwnProps> = ({
const lang = useLang();
let content: string | undefined = '';
let content: TeactNode | undefined;
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
if (isSavedMessages) {

View File

@ -1,4 +1,4 @@
import React, { FC, memo } from '../../lib/teact/teact';
import React, { FC, memo, TeactNode } from '../../lib/teact/teact';
import { withGlobal } from '../../global';
import { ApiChat, ApiUser } from '../../api/types';
@ -42,7 +42,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
}) => {
const lang = useLang();
let iconElement: any;
let iconElement: TeactNode | undefined;
let titleText: any;
if (icon && title) {

View File

@ -1,4 +1,4 @@
import React, { FC, memo } from '../../lib/teact/teact';
import React, { FC, memo, TeactNode } from '../../lib/teact/teact';
import {
ApiChat, ApiMediaFormat, ApiPhoto, ApiUser,
@ -68,7 +68,7 @@ const ProfilePhoto: FC<OwnProps> = ({
const avatarBlobUrl = useMedia(avatarMediaHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
const imageSrc = photoBlobUrl || avatarBlobUrl || photo?.thumbnail?.dataUri;
let content: string | undefined = '';
let content: TeactNode | undefined;
if (isSavedMessages) {
content = <i className="icon-avatar-saved-messages" />;

View File

@ -21,7 +21,7 @@ const CodeBlock: FC<OwnProps> = ({ text, language, noCopy }) => {
const [isWordWrap, setWordWrap] = useState(true);
const { result: highlighted } = useAsync(() => {
if (!language) return Promise.resolve();
if (!language) return Promise.resolve(undefined);
return import('../../../util/highlightCode')
.then((lib) => lib.default(text, language));
}, [language, text]);

View File

@ -76,7 +76,7 @@ export default function renderText(
function escapeHtml(textParts: TextPart[]): TextPart[] {
const divEl = document.createElement('div');
return textParts.reduce((result, part) => {
return textParts.reduce((result: TextPart[], part) => {
if (typeof part !== 'string') {
result.push(part);
return result;
@ -86,7 +86,7 @@ function escapeHtml(textParts: TextPart[]): TextPart[] {
result.push(divEl.innerHTML);
return result;
}, [] as TextPart[]);
}, []);
}
function replaceEmojis(textParts: TextPart[], size: 'big' | 'small', type: 'jsx' | 'html'): TextPart[] {
@ -174,7 +174,7 @@ function addLineBreaks(textParts: TextPart[], type: 'jsx' | 'html'): TextPart[]
}
function addHighlight(textParts: TextPart[], highlight: string | undefined): TextPart[] {
return textParts.reduce((result, part) => {
return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string' || !highlight) {
result.push(part);
return result;
@ -198,13 +198,13 @@ function addHighlight(textParts: TextPart[], highlight: string | undefined): Tex
newParts.push(part.substring(queryPosition + highlight.length));
return [...result, ...newParts];
}, [] as TextPart[]);
}, []);
}
const RE_LINK = new RegExp(`${RE_LINK_TEMPLATE}|${RE_MENTION_TEMPLATE}`, 'ig');
function addLinks(textParts: TextPart[]): TextPart[] {
return textParts.reduce((result, part) => {
return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string') {
result.push(part);
return result;
@ -244,11 +244,11 @@ function addLinks(textParts: TextPart[]): TextPart[] {
content.push(part.substring(lastIndex));
return [...result, ...content];
}, [] as TextPart[]);
}, []);
}
function replaceSimpleMarkdown(textParts: TextPart[], type: 'jsx' | 'html'): TextPart[] {
return textParts.reduce((result, part) => {
return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string') {
result.push(part);
return result;
@ -280,7 +280,7 @@ function replaceSimpleMarkdown(textParts: TextPart[], type: 'jsx' | 'html'): Tex
return entityResult;
}, result);
}, [] as TextPart[]);
}, []);
}
export function areLinesWrapping(text: string, element: HTMLElement) {

View File

@ -1,15 +1,15 @@
import React, { FC, memo } from '../../../../lib/teact/teact';
import React, { FC, memo, TeactNode } from '../../../../lib/teact/teact';
import { ApiWebDocument } from '../../../../api/types';
import { getFirstLetters } from '../../../../util/textFormat';
import renderText from '../../../common/helpers/renderText';
import useMedia from '../../../../hooks/useMedia';
import { preventMessageInputBlurWithBubbling } from '../../helpers/preventMessageInputBlur';
import ListItem from '../../../ui/ListItem';
import './BaseResult.scss';
import { preventMessageInputBlurWithBubbling } from '../../helpers/preventMessageInputBlur';
export type OwnProps = {
focus?: boolean;
@ -30,7 +30,7 @@ const BaseResult: FC<OwnProps> = ({
transitionClassNames = '',
onClick,
}) => {
let content: string | undefined = '';
let content: TeactNode | undefined;
const thumbnailDataUrl = useMedia(thumbnail ? `webDocument:${thumbnail.url}` : undefined);
thumbUrl = thumbUrl || thumbnailDataUrl;

View File

@ -1,5 +1,7 @@
import { ChangeEvent } from 'react';
import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import React, {
FC, memo, TeactNode, useCallback,
} from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang';
@ -13,7 +15,7 @@ type OwnProps = {
id?: string;
name?: string;
value?: string;
label: string;
label: TeactNode;
subLabel?: string;
checked: boolean;
disabled?: boolean;

View File

@ -1,5 +1,5 @@
import React, {
FC, memo, useCallback, useRef,
FC, memo, TeactNode, useCallback, useRef,
} from '../../lib/teact/teact';
import { TextPart } from '../../types';
@ -15,7 +15,7 @@ type OwnProps = {
onClose: () => void;
onCloseAnimationEnd?: () => void;
title?: string;
header?: FC;
header?: TeactNode;
textParts?: TextPart[];
text?: string;
confirmLabel?: string;

View File

@ -1,6 +1,6 @@
import { RefObject } from 'react';
import React, {
FC, useEffect, useRef,
FC, TeactNode, useEffect, useRef,
} from '../../lib/teact/teact';
import { TextPart } from '../../types';
@ -25,7 +25,7 @@ type OwnProps = {
title?: string | TextPart[];
className?: string;
isOpen?: boolean;
header?: any;
header?: TeactNode;
hasCloseButton?: boolean;
noBackdrop?: boolean;
children: React.ReactNode;

View File

@ -1,5 +1,5 @@
import { ChangeEvent } from 'react';
import React, { FC, memo } from '../../lib/teact/teact';
import React, { FC, memo, TeactNode } from '../../lib/teact/teact';
import buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang';
@ -11,7 +11,7 @@ import './Radio.scss';
type OwnProps = {
id?: string;
name: string;
label: string;
label: TeactNode;
subLabel?: string;
value: string;
checked: boolean;

View File

@ -1,10 +1,12 @@
import { ChangeEvent } from 'react';
import React, { FC, useCallback, memo } from '../../lib/teact/teact';
import React, {
FC, useCallback, memo, TeactNode,
} from '../../lib/teact/teact';
import Radio from './Radio';
export type IRadioOption = {
label: string;
label: TeactNode;
subLabel?: string;
value: string;
hidden?: boolean;

View File

@ -1,3 +1,4 @@
import type { ReactElement } from 'react';
import { DEBUG, DEBUG_MORE } from '../../config';
import {
fastRaf, fastRafPrimary, onTickEnd, onTickEndPrimary, throttleWithPrimaryRaf, throttleWithRaf,
@ -102,6 +103,9 @@ export type VirtualRealElement =
| VirtualElementComponent;
export type VirtualElementChildren = VirtualElement[];
// Compatibility with JSX types
export type TeactNode = ReactElement | string | number | boolean;
const Fragment = Symbol('Fragment');
const DEBUG_RENDER_THRESHOLD = 7;
@ -712,10 +716,7 @@ export function memo<T extends FC>(Component: T, debugKey?: string) {
} as T;
}
// We need to keep it here for JSX.
const Teact = {
export default {
createElement,
Fragment,
};
export default Teact;

View File

@ -1,3 +1,4 @@
import { TeactNode } from '../lib/teact/teact';
import {
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm,
ApiChatInviteImporter,
@ -5,7 +6,7 @@ import {
ApiLanguage, ApiMessage, ApiShippingAddress, ApiStickerSet,
} from '../api/types';
export type TextPart = string | JSX.Element;
export type TextPart = TeactNode;
export enum LoadMoreDirection {
Backwards,

View File

@ -1,6 +1,6 @@
import type { Element, Root } from 'hast';
import { lowlight } from 'lowlight/lib/core';
import Teact from '../lib/teact/teact';
import Teact, { TeactNode } from '../lib/teact/teact';
// First element in alias array MUST BE a language package name
const SUPPORTED_LANGUAGES = {
@ -85,7 +85,7 @@ async function ensureLanguage(language: string) {
return true;
}
function treeToElements(tree: Element | Root): JSX.Element {
function treeToElements(tree: Element | Root): TeactNode {
const children = tree.children.map((child) => {
if (child.type === 'text') {
return child.value;
@ -97,12 +97,12 @@ function treeToElements(tree: Element | Root): JSX.Element {
}).filter(Boolean);
if (tree.type === 'root') {
return Teact.createElement('code', { className: 'hljs custom-scroll-x' }, children);
return Teact.createElement('code', { className: 'hljs custom-scroll-x' }, children) as unknown as TeactNode;
}
const name = tree.tagName;
const classNameArray = tree.properties?.className as string[];
const className = classNameArray?.join(' ');
return Teact.createElement(name, { className }, children);
return Teact.createElement(name, { className }, children) as unknown as TeactNode;
}