[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 { 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 { import {
ApiChat, ApiMediaFormat, ApiPhoto, ApiUser, ApiUserStatus, ApiChat, ApiMediaFormat, ApiPhoto, ApiUser, ApiUserStatus,
@ -75,7 +77,7 @@ const Avatar: FC<OwnProps> = ({
const lang = useLang(); const lang = useLang();
let content: string | undefined = ''; let content: TeactNode | undefined;
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text); const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
if (isSavedMessages) { 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 { withGlobal } from '../../global';
import { ApiChat, ApiUser } from '../../api/types'; import { ApiChat, ApiUser } from '../../api/types';
@ -42,7 +42,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
}) => { }) => {
const lang = useLang(); const lang = useLang();
let iconElement: any; let iconElement: TeactNode | undefined;
let titleText: any; let titleText: any;
if (icon && title) { 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 { import {
ApiChat, ApiMediaFormat, ApiPhoto, ApiUser, ApiChat, ApiMediaFormat, ApiPhoto, ApiUser,
@ -68,7 +68,7 @@ const ProfilePhoto: FC<OwnProps> = ({
const avatarBlobUrl = useMedia(avatarMediaHash, false, ApiMediaFormat.BlobUrl, lastSyncTime); const avatarBlobUrl = useMedia(avatarMediaHash, false, ApiMediaFormat.BlobUrl, lastSyncTime);
const imageSrc = photoBlobUrl || avatarBlobUrl || photo?.thumbnail?.dataUri; const imageSrc = photoBlobUrl || avatarBlobUrl || photo?.thumbnail?.dataUri;
let content: string | undefined = ''; let content: TeactNode | undefined;
if (isSavedMessages) { if (isSavedMessages) {
content = <i className="icon-avatar-saved-messages" />; 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 [isWordWrap, setWordWrap] = useState(true);
const { result: highlighted } = useAsync(() => { const { result: highlighted } = useAsync(() => {
if (!language) return Promise.resolve(); if (!language) return Promise.resolve(undefined);
return import('../../../util/highlightCode') return import('../../../util/highlightCode')
.then((lib) => lib.default(text, language)); .then((lib) => lib.default(text, language));
}, [language, text]); }, [language, text]);

View File

@ -76,7 +76,7 @@ export default function renderText(
function escapeHtml(textParts: TextPart[]): TextPart[] { function escapeHtml(textParts: TextPart[]): TextPart[] {
const divEl = document.createElement('div'); const divEl = document.createElement('div');
return textParts.reduce((result, part) => { return textParts.reduce((result: TextPart[], part) => {
if (typeof part !== 'string') { if (typeof part !== 'string') {
result.push(part); result.push(part);
return result; return result;
@ -86,7 +86,7 @@ function escapeHtml(textParts: TextPart[]): TextPart[] {
result.push(divEl.innerHTML); result.push(divEl.innerHTML);
return result; return result;
}, [] as TextPart[]); }, []);
} }
function replaceEmojis(textParts: TextPart[], size: 'big' | 'small', type: 'jsx' | 'html'): 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[] { function addHighlight(textParts: TextPart[], highlight: string | undefined): TextPart[] {
return textParts.reduce((result, part) => { return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string' || !highlight) { if (typeof part !== 'string' || !highlight) {
result.push(part); result.push(part);
return result; return result;
@ -198,13 +198,13 @@ function addHighlight(textParts: TextPart[], highlight: string | undefined): Tex
newParts.push(part.substring(queryPosition + highlight.length)); newParts.push(part.substring(queryPosition + highlight.length));
return [...result, ...newParts]; return [...result, ...newParts];
}, [] as TextPart[]); }, []);
} }
const RE_LINK = new RegExp(`${RE_LINK_TEMPLATE}|${RE_MENTION_TEMPLATE}`, 'ig'); const RE_LINK = new RegExp(`${RE_LINK_TEMPLATE}|${RE_MENTION_TEMPLATE}`, 'ig');
function addLinks(textParts: TextPart[]): TextPart[] { function addLinks(textParts: TextPart[]): TextPart[] {
return textParts.reduce((result, part) => { return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string') { if (typeof part !== 'string') {
result.push(part); result.push(part);
return result; return result;
@ -244,11 +244,11 @@ function addLinks(textParts: TextPart[]): TextPart[] {
content.push(part.substring(lastIndex)); content.push(part.substring(lastIndex));
return [...result, ...content]; return [...result, ...content];
}, [] as TextPart[]); }, []);
} }
function replaceSimpleMarkdown(textParts: TextPart[], type: 'jsx' | 'html'): TextPart[] { function replaceSimpleMarkdown(textParts: TextPart[], type: 'jsx' | 'html'): TextPart[] {
return textParts.reduce((result, part) => { return textParts.reduce<TextPart[]>((result, part) => {
if (typeof part !== 'string') { if (typeof part !== 'string') {
result.push(part); result.push(part);
return result; return result;
@ -280,7 +280,7 @@ function replaceSimpleMarkdown(textParts: TextPart[], type: 'jsx' | 'html'): Tex
return entityResult; return entityResult;
}, result); }, result);
}, [] as TextPart[]); }, []);
} }
export function areLinesWrapping(text: string, element: HTMLElement) { 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 { ApiWebDocument } from '../../../../api/types';
import { getFirstLetters } from '../../../../util/textFormat'; import { getFirstLetters } from '../../../../util/textFormat';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
import useMedia from '../../../../hooks/useMedia'; import useMedia from '../../../../hooks/useMedia';
import { preventMessageInputBlurWithBubbling } from '../../helpers/preventMessageInputBlur';
import ListItem from '../../../ui/ListItem'; import ListItem from '../../../ui/ListItem';
import './BaseResult.scss'; import './BaseResult.scss';
import { preventMessageInputBlurWithBubbling } from '../../helpers/preventMessageInputBlur';
export type OwnProps = { export type OwnProps = {
focus?: boolean; focus?: boolean;
@ -30,7 +30,7 @@ const BaseResult: FC<OwnProps> = ({
transitionClassNames = '', transitionClassNames = '',
onClick, onClick,
}) => { }) => {
let content: string | undefined = ''; let content: TeactNode | undefined;
const thumbnailDataUrl = useMedia(thumbnail ? `webDocument:${thumbnail.url}` : undefined); const thumbnailDataUrl = useMedia(thumbnail ? `webDocument:${thumbnail.url}` : undefined);
thumbUrl = thumbUrl || thumbnailDataUrl; thumbUrl = thumbUrl || thumbnailDataUrl;

View File

@ -1,5 +1,7 @@
import { ChangeEvent } from 'react'; 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 buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -13,7 +15,7 @@ type OwnProps = {
id?: string; id?: string;
name?: string; name?: string;
value?: string; value?: string;
label: string; label: TeactNode;
subLabel?: string; subLabel?: string;
checked: boolean; checked: boolean;
disabled?: boolean; disabled?: boolean;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { ChangeEvent } from 'react'; 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 buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -11,7 +11,7 @@ import './Radio.scss';
type OwnProps = { type OwnProps = {
id?: string; id?: string;
name: string; name: string;
label: string; label: TeactNode;
subLabel?: string; subLabel?: string;
value: string; value: string;
checked: boolean; checked: boolean;

View File

@ -1,10 +1,12 @@
import { ChangeEvent } from 'react'; 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'; import Radio from './Radio';
export type IRadioOption = { export type IRadioOption = {
label: string; label: TeactNode;
subLabel?: string; subLabel?: string;
value: string; value: string;
hidden?: boolean; hidden?: boolean;

View File

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

View File

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

View File

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