diff --git a/src/components/left/settings/SettingsNotifications.tsx b/src/components/left/settings/SettingsNotifications.tsx index 3eade46bf..c54c62f59 100644 --- a/src/components/left/settings/SettingsNotifications.tsx +++ b/src/components/left/settings/SettingsNotifications.tsx @@ -59,7 +59,7 @@ const SettingsNotifications: FC = ({ loadNotificationSettings(); }, [loadNotificationSettings]); - const runDebounced = useDebounce(500, false, true); + const runDebounced = useDebounce(500, true); const handleSettingsChange = useCallback(( e: ChangeEvent, diff --git a/src/components/mediaViewer/MediaViewerSlides.tsx b/src/components/mediaViewer/MediaViewerSlides.tsx index 810484c5b..9920705f9 100644 --- a/src/components/mediaViewer/MediaViewerSlides.tsx +++ b/src/components/mediaViewer/MediaViewerSlides.tsx @@ -104,9 +104,9 @@ const MediaViewerSlides: FC = ({ forceUpdate(); }, [forceUpdate]); - const debounceSetMessage = useDebounce(DEBOUNCE_MESSAGE, false); - const debounceSwipe = useDebounce(DEBOUNCE_SWIPE, false); - const debounceActive = useDebounce(DEBOUNCE_ACTIVE, false); + const debounceSetMessage = useDebounce(DEBOUNCE_MESSAGE, true); + const debounceSwipe = useDebounce(DEBOUNCE_SWIPE, true); + const debounceActive = useDebounce(DEBOUNCE_ACTIVE, true); const handleToggleFooterVisibility = useCallback(() => { if (IS_TOUCH_ENV && (isPhoto || isGif) && hasFooter) { diff --git a/src/components/middle/composer/WebPagePreview.tsx b/src/components/middle/composer/WebPagePreview.tsx index 51777a978..bfd66bd8c 100644 --- a/src/components/middle/composer/WebPagePreview.tsx +++ b/src/components/middle/composer/WebPagePreview.tsx @@ -1,6 +1,4 @@ -import React, { - FC, memo, useEffect, useMemo, -} from '../../../lib/teact/teact'; +import React, { FC, memo, useEffect } from '../../../lib/teact/teact'; import { getDispatch, withGlobal } from '../../../lib/teact/teactn'; import { ApiMessage, ApiMessageEntityTypes, ApiWebPage } from '../../../api/types'; @@ -12,6 +10,7 @@ import parseMessageInput from '../../../util/parseMessageInput'; import useOnChange from '../../../hooks/useOnChange'; import useShowTransition from '../../../hooks/useShowTransition'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useDebouncedMemo from '../../../hooks/useDebouncedMemo'; import buildClassName from '../../../util/buildClassName'; import WebPage from '../message/WebPage'; @@ -32,6 +31,7 @@ type StateProps = { theme: ISettings['theme']; }; +const DEBOUNCE_MS = 300; const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i'); const WebPagePreview: FC = ({ @@ -49,7 +49,7 @@ const WebPagePreview: FC = ({ toggleMessageWebPage, } = getDispatch(); - const link = useMemo(() => { + const link = useDebouncedMemo(() => { const { text, entities } = parseMessageInput(messageText); const linkEntity = entities && entities.find(({ type }) => type === ApiMessageEntityTypes.TextUrl); @@ -63,7 +63,7 @@ const WebPagePreview: FC = ({ } return undefined; - }, [messageText]); + }, DEBOUNCE_MS, [messageText]); useEffect(() => { if (link) { diff --git a/src/components/middle/composer/hooks/useInlineBotTooltip.ts b/src/components/middle/composer/hooks/useInlineBotTooltip.ts index c4ae6365e..48ea4068e 100644 --- a/src/components/middle/composer/hooks/useInlineBotTooltip.ts +++ b/src/components/middle/composer/hooks/useInlineBotTooltip.ts @@ -3,25 +3,29 @@ import { getDispatch } from '../../../../lib/teact/teactn'; import { InlineBotSettings } from '../../../../types'; import useFlag from '../../../../hooks/useFlag'; import usePrevious from '../../../../hooks/usePrevious'; +import useDebouncedMemo from '../../../../hooks/useDebouncedMemo'; -const tempEl = document.createElement('div'); +const DEBOUNCE_MS = 300; const INLINE_BOT_QUERY_REGEXP = /^@([a-z0-9_]{1,32})[\u00A0\u0020]+(.*)/i; const HAS_NEW_LINE = /^@([a-z0-9_]{1,32})[\u00A0\u0020]+\n{2,}/i; +const tempEl = document.createElement('div'); + export default function useInlineBotTooltip( isAllowed: boolean, chatId: string, html: string, inlineBots?: Record, ) { - const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); - const text = getPlainText(html); const { queryInlineBot, resetInlineBot } = getDispatch(); - const { username, query, canShowHelp } = parseStartWithUsernameString(text); - const usernameLowered = username.toLowerCase(); + + const [isOpen, markIsOpen, unmarkIsOpen] = useFlag(); + const { + username, query, canShowHelp, usernameLowered, + } = useDebouncedMemo(() => parseBotQuery(html), DEBOUNCE_MS, [html]) || {}; const prevQuery = usePrevious(query); const prevUsername = usePrevious(username); - const inlineBotData = inlineBots?.[usernameLowered]; + const inlineBotData = usernameLowered ? inlineBots?.[usernameLowered] : undefined; const { id: botId, switchPm, @@ -63,14 +67,33 @@ export default function useInlineBotTooltip( return { isOpen, - closeTooltip: unmarkIsOpen, - loadMore, - username, id: botId, isGallery, switchPm, results, + closeTooltip: unmarkIsOpen, help: canShowHelp && help ? `@${username} ${help}` : undefined, + loadMore, + }; +} + +function parseBotQuery(html: string) { + const text = getPlainText(html); + const result = text.match(INLINE_BOT_QUERY_REGEXP); + if (!result) { + return { + username: '', + query: '', + canShowHelp: false, + usernameLowered: '', + }; + } + + return { + username: result[1], + query: result[2], + canShowHelp: result[2] === '' && !text.match(HAS_NEW_LINE), + usernameLowered: result[1].toLowerCase(), }; } @@ -79,16 +102,3 @@ function getPlainText(html: string) { return tempEl.innerText; } - -function parseStartWithUsernameString(text: string) { - const result = text.match(INLINE_BOT_QUERY_REGEXP); - if (!result) { - return { username: '', query: '', canShowHelp: false }; - } - - return { - username: result[1], - query: result[2], - canShowHelp: result[2] === '' && !text.match(HAS_NEW_LINE), - }; -} diff --git a/src/components/middle/hooks/useStickyDates.ts b/src/components/middle/hooks/useStickyDates.ts index ad1d49065..84135b875 100644 --- a/src/components/middle/hooks/useStickyDates.ts +++ b/src/components/middle/hooks/useStickyDates.ts @@ -13,7 +13,7 @@ export default function useStickyDates() { // so we will add `position: sticky` only after first scroll. There would be no animation on the first show though. const [isScrolled, markIsScrolled] = useFlag(false); - const runDebounced = useDebounce(DEBOUNCE, false); + const runDebounced = useDebounce(DEBOUNCE, true); const updateStickyDates = useCallback((container: HTMLDivElement, hasTools?: boolean) => { markIsScrolled(); diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index 08cc8ba25..1ea87deff 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -2,8 +2,8 @@ import { useMemo } from '../lib/teact/teact'; import { debounce } from '../util/schedulers'; -export default function useDebounce(ms: number, shouldRunFirst?: boolean, shouldRunLast?: boolean) { +export default function useDebounce(ms: number, noFirst?: boolean, noLast?: boolean) { return useMemo(() => { - return debounce((cb) => cb(), ms, shouldRunFirst, shouldRunLast); - }, [ms, shouldRunFirst, shouldRunLast]); + return debounce((cb) => cb(), ms, !noFirst, !noLast); + }, [ms, noFirst, noLast]); } diff --git a/src/hooks/useDebouncedMemo.ts b/src/hooks/useDebouncedMemo.ts new file mode 100644 index 000000000..168e08926 --- /dev/null +++ b/src/hooks/useDebouncedMemo.ts @@ -0,0 +1,28 @@ +import { useState } from '../lib/teact/teact'; + +import useDebounce from './useDebounce'; +import useOnChange from './useOnChange'; +import useHeavyAnimationCheck from './useHeavyAnimationCheck'; +import useFlag from './useFlag'; + +export default function useDebouncedMemo( + resolverFn: () => R, ms: number, dependencies: D, +): R | undefined { + const runDebounced = useDebounce(ms, true); + const [value, setValue] = useState(); + const [isFrozen, freeze, unfreeze] = useFlag(); + + useHeavyAnimationCheck(freeze, unfreeze); + + useOnChange(() => { + if (isFrozen) { + return; + } + + runDebounced(() => { + setValue(resolverFn()); + }); + }, [...dependencies, isFrozen]); + + return value; +} diff --git a/src/hooks/useThrottledMemo.ts b/src/hooks/useThrottledMemo.ts index 78bd8eba0..a849b8fa4 100644 --- a/src/hooks/useThrottledMemo.ts +++ b/src/hooks/useThrottledMemo.ts @@ -5,7 +5,9 @@ import useOnChange from './useOnChange'; import useHeavyAnimationCheck from './useHeavyAnimationCheck'; import useFlag from './useFlag'; -export default (resolverFn: () => R, ms: number, dependencies: D) => { +export default function useThrottledMemo( + resolverFn: () => R, ms: number, dependencies: D, +): R | undefined { const runThrottled = useThrottle(ms, true); const [value, setValue] = useState(); const [isFrozen, freeze, unfreeze] = useFlag(); @@ -20,7 +22,7 @@ export default (resolverFn: () => R, ms: number, runThrottled(() => { setValue(resolverFn()); }); - }, dependencies.concat([isFrozen])); + }, [...dependencies, isFrozen]); return value; -}; +}