Header Pinned Message: Refactor and fix getting stuck on the same message
This commit is contained in:
parent
cb6c1faa86
commit
bceec156b5
@ -1,6 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef,
|
memo, useCallback, useEffect, useMemo, useRef, useUnmountCleanup,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import type {
|
|||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
import type { FocusDirection, ThreadId } from '../../types';
|
import type { FocusDirection, ThreadId } from '../../types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import { getChatTitle, getMessageHtmlId, isJoinedChannelMessage } from '../../global/helpers';
|
import { getChatTitle, getMessageHtmlId, isJoinedChannelMessage } from '../../global/helpers';
|
||||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
@ -57,7 +57,7 @@ type OwnProps = {
|
|||||||
isLastInList?: boolean;
|
isLastInList?: boolean;
|
||||||
isInsideTopic?: boolean;
|
isInsideTopic?: boolean;
|
||||||
memoFirstUnreadIdRef?: { current: number | undefined };
|
memoFirstUnreadIdRef?: { current: number | undefined };
|
||||||
onPinnedIntersectionChange?: PinnedIntersectionChangedCallback;
|
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -102,7 +102,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
onPinnedIntersectionChange,
|
onIntersectPinnedMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openPremiumModal, requestConfetti, checkGiftCode, getReceipt, openStarsTransactionFromGift,
|
openPremiumModal, requestConfetti, checkGiftCode, getReceipt, openStarsTransactionFromGift,
|
||||||
@ -121,13 +121,11 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight, isJustAdded);
|
useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight, isJustAdded);
|
||||||
|
|
||||||
useEffect(() => {
|
useUnmountCleanup(() => {
|
||||||
if (!message.isPinned) return undefined;
|
if (message.isPinned) {
|
||||||
|
onIntersectPinnedMessage?.({ viewportPinnedIdsToRemove: [message.id] });
|
||||||
return () => {
|
}
|
||||||
onPinnedIntersectionChange?.({ viewportPinnedIdsToRemove: [message.id], isUnmount: true });
|
});
|
||||||
};
|
|
||||||
}, [onPinnedIntersectionChange, message.isPinned, message.id]);
|
|
||||||
|
|
||||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||||
@ -287,10 +285,14 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
{oldLang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')}
|
{oldLang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')}
|
||||||
</strong>
|
</strong>
|
||||||
<span className="action-message-subtitle">
|
<span className="action-message-subtitle">
|
||||||
{targetChat && renderText(oldLang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed
|
{targetChat && renderText(
|
||||||
? 'BoostingReceivedPrizeFrom' : 'BoostingYouHaveUnclaimedPrize',
|
oldLang(
|
||||||
getChatTitle(oldLang, targetChat)),
|
isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed
|
||||||
['simple_markdown'])}
|
? 'BoostingReceivedPrizeFrom' : 'BoostingYouHaveUnclaimedPrize',
|
||||||
|
getChatTitle(oldLang, targetChat),
|
||||||
|
),
|
||||||
|
['simple_markdown'],
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="action-message-subtitle">
|
<span className="action-message-subtitle">
|
||||||
{renderText(oldLang(
|
{renderText(oldLang(
|
||||||
|
|||||||
@ -3,17 +3,16 @@ import React, { memo } from '../../lib/teact/teact';
|
|||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../api/types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
|
import type { Signal } from '../../util/signals';
|
||||||
|
|
||||||
import {
|
import { getMessageIsSpoiler, getMessageMediaHash, getMessageSingleInlineButton } from '../../global/helpers';
|
||||||
getMessageIsSpoiler,
|
|
||||||
getMessageMediaHash, getMessageSingleInlineButton,
|
|
||||||
} from '../../global/helpers';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||||
import { getPictogramDimensions, REM } from '../common/helpers/mediaDimensions';
|
import { getPictogramDimensions, REM } from '../common/helpers/mediaDimensions';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import renderKeyboardButtonText from './composer/helpers/renderKeyboardButtonText';
|
import renderKeyboardButtonText from './composer/helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
|
import useDerivedState from '../../hooks/useDerivedState';
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
@ -46,13 +45,13 @@ type OwnProps = {
|
|||||||
onUnpinMessage?: (id: number) => void;
|
onUnpinMessage?: (id: number) => void;
|
||||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
onAllPinnedClick?: () => void;
|
onAllPinnedClick?: () => void;
|
||||||
isLoading?: boolean;
|
getLoadingPinnedId: Signal<number | undefined>;
|
||||||
isFullWidth?: boolean;
|
isFullWidth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const HeaderPinnedMessage: FC<OwnProps> = ({
|
const HeaderPinnedMessage: FC<OwnProps> = ({
|
||||||
message, count, index, customTitle, className, onUnpinMessage, onClick, onAllPinnedClick,
|
message, count, index, customTitle, className, onUnpinMessage, onClick, onAllPinnedClick,
|
||||||
isLoading, isFullWidth,
|
getLoadingPinnedId, isFullWidth,
|
||||||
}) => {
|
}) => {
|
||||||
const { clickBotInlineButton } = getActions();
|
const { clickBotInlineButton } = getActions();
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -60,6 +59,8 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
|
|||||||
const mediaThumbnail = useThumbnail(message);
|
const mediaThumbnail = useThumbnail(message);
|
||||||
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'));
|
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'));
|
||||||
const isSpoiler = getMessageIsSpoiler(message);
|
const isSpoiler = getMessageIsSpoiler(message);
|
||||||
|
|
||||||
|
const isLoading = Boolean(useDerivedState(getLoadingPinnedId));
|
||||||
const canRenderLoader = useAsyncRendering([isLoading], SHOW_LOADER_DELAY);
|
const canRenderLoader = useAsyncRendering([isLoading], SHOW_LOADER_DELAY);
|
||||||
const shouldShowLoader = canRenderLoader && isLoading;
|
const shouldShowLoader = canRenderLoader && isLoading;
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
beginHeavyAnimation,
|
beginHeavyAnimation, memo, useEffect, useMemo, useRef,
|
||||||
memo,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { addExtraClass, removeExtraClass } from '../../lib/teact/teact-dom';
|
import { addExtraClass, removeExtraClass } from '../../lib/teact/teact-dom';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChatFullInfo,
|
ApiChatFullInfo, ApiMessage, ApiRestrictionReason, ApiTopic,
|
||||||
ApiMessage, ApiRestrictionReason, ApiTopic,
|
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { Signal } from '../../util/signals';
|
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { LoadMoreDirection, type ThreadId } from '../../types';
|
import { LoadMoreDirection, type ThreadId } from '../../types';
|
||||||
|
|
||||||
@ -98,8 +92,7 @@ type OwnProps = {
|
|||||||
hasTools?: boolean;
|
hasTools?: boolean;
|
||||||
withBottomShift?: boolean;
|
withBottomShift?: boolean;
|
||||||
withDefaultBg: boolean;
|
withDefaultBg: boolean;
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
getForceNextPinnedInHeader: Signal<boolean | undefined>;
|
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,11 +178,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
noMessageSendingAnimation,
|
noMessageSendingAnimation,
|
||||||
isServiceNotificationsChat,
|
isServiceNotificationsChat,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
getForceNextPinnedInHeader,
|
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
areAdsEnabled,
|
areAdsEnabled,
|
||||||
channelJoinInfo,
|
channelJoinInfo,
|
||||||
onPinnedIntersectionChange,
|
onIntersectPinnedMessage,
|
||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
}) => {
|
}) => {
|
||||||
@ -418,9 +410,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
runDebouncedForScroll(() => {
|
runDebouncedForScroll(() => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
const forceNextPinnedInHeader = getForceNextPinnedInHeader() && !selectTabState(global).focusedMessage?.chatId;
|
|
||||||
if (forceNextPinnedInHeader) {
|
const isFocusing = Boolean(selectTabState(global).focusedMessage?.chatId);
|
||||||
onPinnedIntersectionChange({ hasScrolled: true });
|
if (!isFocusing) {
|
||||||
|
onIntersectPinnedMessage({ shouldCancelWaiting: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.parentElement) {
|
if (!container.parentElement) {
|
||||||
@ -731,7 +724,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||||
onScrollDownToggle={onScrollDownToggle}
|
onScrollDownToggle={onScrollDownToggle}
|
||||||
onNotchToggle={onNotchToggle}
|
onNotchToggle={onNotchToggle}
|
||||||
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Loading color="white" backgroundColor="dark" />
|
<Loading color="white" backgroundColor="dark" />
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type { MessageListType } from '../../global/types';
|
|||||||
import type { ThreadId } from '../../types';
|
import type { ThreadId } from '../../types';
|
||||||
import type { Signal } from '../../util/signals';
|
import type { Signal } from '../../util/signals';
|
||||||
import type { MessageDateGroup } from './helpers/groupMessages';
|
import type { MessageDateGroup } from './helpers/groupMessages';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import { SCHEDULED_WHEN_ONLINE } from '../../config';
|
import { SCHEDULED_WHEN_ONLINE } from '../../config';
|
||||||
@ -62,7 +62,7 @@ interface OwnProps {
|
|||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
onScrollDownToggle: BooleanToVoidFunction;
|
onScrollDownToggle: BooleanToVoidFunction;
|
||||||
onNotchToggle: AnyToVoidFunction;
|
onNotchToggle: AnyToVoidFunction;
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
@ -94,7 +94,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
onScrollDownToggle,
|
onScrollDownToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
onPinnedIntersectionChange,
|
onIntersectPinnedMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const { openHistoryCalendar } = getActions();
|
const { openHistoryCalendar } = getActions();
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef, onPinnedIntersectionChange, chatId);
|
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef, onIntersectPinnedMessage, chatId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
withHistoryTriggers,
|
withHistoryTriggers,
|
||||||
@ -180,7 +180,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
appearanceOrder={messageCountToAnimate - ++appearanceIndex}
|
||||||
isJustAdded={isLastInList && isNewMessage}
|
isJustAdded={isLastInList && isNewMessage}
|
||||||
isLastInList={isLastInList}
|
isLastInList={isLastInList}
|
||||||
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||||
/>,
|
/>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
isLastInDocumentGroup={position.isLastInDocumentGroup}
|
isLastInDocumentGroup={position.isLastInDocumentGroup}
|
||||||
isLastInList={position.isLastInList}
|
isLastInList={position.isLastInList}
|
||||||
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
memoFirstUnreadIdRef={memoFirstUnreadIdRef}
|
||||||
onPinnedIntersectionChange={onPinnedIntersectionChange}
|
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||||
getIsMessageListReady={getIsReady}
|
getIsMessageListReady={getIsReady}
|
||||||
/>,
|
/>,
|
||||||
message.id === threadId && (
|
message.id === threadId && (
|
||||||
|
|||||||
@ -154,7 +154,6 @@ type StateProps = {
|
|||||||
shouldJoinToSend?: boolean;
|
shouldJoinToSend?: boolean;
|
||||||
shouldSendJoinRequest?: boolean;
|
shouldSendJoinRequest?: boolean;
|
||||||
pinnedIds?: number[];
|
pinnedIds?: number[];
|
||||||
topMessageId?: number;
|
|
||||||
canUnpin?: boolean;
|
canUnpin?: boolean;
|
||||||
canUnblock?: boolean;
|
canUnblock?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
@ -216,7 +215,6 @@ function MiddleColumn({
|
|||||||
shouldSendJoinRequest,
|
shouldSendJoinRequest,
|
||||||
shouldLoadFullChat,
|
shouldLoadFullChat,
|
||||||
pinnedIds,
|
pinnedIds,
|
||||||
topMessageId,
|
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canUnblock,
|
canUnblock,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
@ -252,12 +250,11 @@ function MiddleColumn({
|
|||||||
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onIntersectionChanged,
|
handleIntersectPinnedMessage,
|
||||||
onFocusPinnedMessage,
|
handleFocusPinnedMessage,
|
||||||
getCurrentPinnedIndexes,
|
getCurrentPinnedIndex,
|
||||||
getLoadingPinnedId,
|
getLoadingPinnedId,
|
||||||
getForceNextPinnedInHeader,
|
} = usePinnedMessage(chatId, threadId, pinnedIds);
|
||||||
} = usePinnedMessage(chatId, threadId, pinnedIds, topMessageId);
|
|
||||||
|
|
||||||
const closeAnimationDuration = isMobile ? LAYER_ANIMATION_DURATION_MS : undefined;
|
const closeAnimationDuration = isMobile ? LAYER_ANIMATION_DURATION_MS : undefined;
|
||||||
const hasTools = hasPinned && (
|
const hasTools = hasPinned && (
|
||||||
@ -287,8 +284,8 @@ function MiddleColumn({
|
|||||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||||
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration);
|
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration);
|
||||||
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration);
|
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration);
|
||||||
const renderingOnPinnedIntersectionChange = usePrevDuringAnimation(
|
const renderingHandleIntersectPinnedMessage = usePrevDuringAnimation(
|
||||||
chatId ? onIntersectionChanged : undefined,
|
chatId ? handleIntersectPinnedMessage : undefined,
|
||||||
closeAnimationDuration,
|
closeAnimationDuration,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -538,9 +535,9 @@ function MiddleColumn({
|
|||||||
isComments={isComments}
|
isComments={isComments}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
getCurrentPinnedIndexes={getCurrentPinnedIndexes}
|
getCurrentPinnedIndex={getCurrentPinnedIndex}
|
||||||
getLoadingPinnedId={getLoadingPinnedId}
|
getLoadingPinnedId={getLoadingPinnedId}
|
||||||
onFocusPinnedMessage={onFocusPinnedMessage}
|
onFocusPinnedMessage={handleFocusPinnedMessage}
|
||||||
/>
|
/>
|
||||||
<Transition
|
<Transition
|
||||||
name={shouldSkipHistoryAnimations ? 'none' : withInterfaceAnimations ? 'slide' : 'fade'}
|
name={shouldSkipHistoryAnimations ? 'none' : withInterfaceAnimations ? 'slide' : 'fade'}
|
||||||
@ -563,8 +560,7 @@ function MiddleColumn({
|
|||||||
isContactRequirePremium={isContactRequirePremium}
|
isContactRequirePremium={isContactRequirePremium}
|
||||||
withBottomShift={withMessageListBottomShift}
|
withBottomShift={withMessageListBottomShift}
|
||||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||||
onPinnedIntersectionChange={renderingOnPinnedIntersectionChange!}
|
onIntersectPinnedMessage={renderingHandleIntersectPinnedMessage!}
|
||||||
getForceNextPinnedInHeader={getForceNextPinnedInHeader}
|
|
||||||
/>
|
/>
|
||||||
<div className={footerClassName}>
|
<div className={footerClassName}>
|
||||||
{renderingCanPost && (
|
{renderingCanPost && (
|
||||||
@ -812,7 +808,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canShowOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
const canShowOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
||||||
|
|
||||||
const isCommentThread = threadId !== MAIN_THREAD_ID && !isSavedDialog && !chat?.isForum;
|
const isCommentThread = threadId !== MAIN_THREAD_ID && !isSavedDialog && !chat?.isForum;
|
||||||
const topMessageId = isCommentThread ? Number(threadId) : undefined;
|
|
||||||
|
|
||||||
const canUnpin = chat && (
|
const canUnpin = chat && (
|
||||||
isPrivate || (
|
isPrivate || (
|
||||||
@ -856,7 +851,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
shouldSendJoinRequest,
|
shouldSendJoinRequest,
|
||||||
shouldLoadFullChat,
|
shouldLoadFullChat,
|
||||||
pinnedIds,
|
pinnedIds,
|
||||||
topMessageId,
|
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canUnblock,
|
canUnblock,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
|
|||||||
@ -91,9 +91,9 @@ type OwnProps = {
|
|||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
getCurrentPinnedIndexes: Signal<Record<string, number>>;
|
getCurrentPinnedIndex: Signal<number>;
|
||||||
getLoadingPinnedId: Signal<number | undefined>;
|
getLoadingPinnedId: Signal<number | undefined>;
|
||||||
onFocusPinnedMessage: (messageId: number) => boolean;
|
onFocusPinnedMessage: (messageId: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -147,7 +147,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
isSyncing,
|
isSyncing,
|
||||||
isSynced,
|
isSynced,
|
||||||
isFetchingDifference,
|
isFetchingDifference,
|
||||||
getCurrentPinnedIndexes,
|
getCurrentPinnedIndex,
|
||||||
getLoadingPinnedId,
|
getLoadingPinnedId,
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
@ -173,9 +173,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
const isBackButtonActive = useRef(true);
|
const isBackButtonActive = useRef(true);
|
||||||
const { isTablet } = useAppLayout();
|
const { isTablet } = useAppLayout();
|
||||||
|
|
||||||
const currentPinnedIndexes = useDerivedState(getCurrentPinnedIndexes);
|
const currentPinnedIndex = useDerivedState(getCurrentPinnedIndex);
|
||||||
const currentPinnedIndex = currentPinnedIndexes[`${chatId}_${threadId}`] || 0;
|
|
||||||
const waitingForPinnedId = useDerivedState(getLoadingPinnedId);
|
|
||||||
const pinnedMessageId = Array.isArray(pinnedMessageIds) ? pinnedMessageIds[currentPinnedIndex] : pinnedMessageIds;
|
const pinnedMessageId = Array.isArray(pinnedMessageIds) ? pinnedMessageIds[currentPinnedIndex] : pinnedMessageIds;
|
||||||
const pinnedMessage = messagesById && pinnedMessageId ? messagesById[pinnedMessageId] : undefined;
|
const pinnedMessage = messagesById && pinnedMessageId ? messagesById[pinnedMessageId] : undefined;
|
||||||
const pinnedMessagesCount = Array.isArray(pinnedMessageIds)
|
const pinnedMessagesCount = Array.isArray(pinnedMessageIds)
|
||||||
@ -233,10 +231,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)]
|
? pinnedMessageIds[cycleRestrict(pinnedMessageIds.length, pinnedMessageIds.indexOf(pinnedMessageId!) - 2)]
|
||||||
: pinnedMessageId!;
|
: pinnedMessageId!;
|
||||||
|
|
||||||
if (onFocusPinnedMessage(messageId)) {
|
if (!getLoadingPinnedId()) {
|
||||||
focusMessage({
|
focusMessage({
|
||||||
chatId, threadId, messageId, noForumTopicPanel: true,
|
chatId, threadId, messageId, noForumTopicPanel: true,
|
||||||
});
|
});
|
||||||
|
onFocusPinnedMessage(messageId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -509,7 +508,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
onUnpinMessage={renderingCanUnpin ? handleUnpinMessage : undefined}
|
onUnpinMessage={renderingCanUnpin ? handleUnpinMessage : undefined}
|
||||||
onClick={handlePinnedMessageClick}
|
onClick={handlePinnedMessageClick}
|
||||||
onAllPinnedClick={handleAllPinnedClick}
|
onAllPinnedClick={handleAllPinnedClick}
|
||||||
isLoading={waitingForPinnedId !== undefined}
|
getLoadingPinnedId={getLoadingPinnedId}
|
||||||
isFullWidth={isPinnedMessagesFullWidth}
|
isFullWidth={isPinnedMessagesFullWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import type { RefObject } from 'react';
|
|||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { MessageListType } from '../../../global/types';
|
import type { MessageListType } from '../../../global/types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './usePinnedMessage';
|
import type { OnIntersectPinnedMessage } from './usePinnedMessage';
|
||||||
|
|
||||||
import { IS_ANDROID } from '../../../util/windowEnvironment';
|
import { IS_ANDROID } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export default function useMessageObservers(
|
|||||||
type: MessageListType,
|
type: MessageListType,
|
||||||
containerRef: RefObject<HTMLDivElement>,
|
containerRef: RefObject<HTMLDivElement>,
|
||||||
memoFirstUnreadIdRef: { current: number | undefined },
|
memoFirstUnreadIdRef: { current: number | undefined },
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback,
|
onIntersectPinnedMessage: OnIntersectPinnedMessage,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@ -44,12 +44,9 @@ export default function useMessageObservers(
|
|||||||
const viewportPinnedIdsToAdd: number[] = [];
|
const viewportPinnedIdsToAdd: number[] = [];
|
||||||
const viewportPinnedIdsToRemove: number[] = [];
|
const viewportPinnedIdsToRemove: number[] = [];
|
||||||
const scheduledToUpdateViews: number[] = [];
|
const scheduledToUpdateViews: number[] = [];
|
||||||
let isReversed = false;
|
|
||||||
|
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
const {
|
const { isIntersecting, target } = entry;
|
||||||
isIntersecting, target, boundingClientRect, rootBounds,
|
|
||||||
} = entry;
|
|
||||||
|
|
||||||
const { dataset } = target as HTMLDivElement;
|
const { dataset } = target as HTMLDivElement;
|
||||||
const messageId = Number(dataset.lastMessageId || dataset.messageId);
|
const messageId = Number(dataset.lastMessageId || dataset.messageId);
|
||||||
@ -58,9 +55,6 @@ export default function useMessageObservers(
|
|||||||
|
|
||||||
if (!isIntersecting) {
|
if (!isIntersecting) {
|
||||||
if (dataset.isPinned) {
|
if (dataset.isPinned) {
|
||||||
if (rootBounds && boundingClientRect.bottom < rootBounds.top) {
|
|
||||||
isReversed = true;
|
|
||||||
}
|
|
||||||
viewportPinnedIdsToRemove.push(albumMainId || messageId);
|
viewportPinnedIdsToRemove.push(albumMainId || messageId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -100,7 +94,7 @@ export default function useMessageObservers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (viewportPinnedIdsToAdd.length || viewportPinnedIdsToRemove.length) {
|
if (viewportPinnedIdsToAdd.length || viewportPinnedIdsToRemove.length) {
|
||||||
onPinnedIntersectionChange({ viewportPinnedIdsToAdd, viewportPinnedIdsToRemove, isReversed });
|
onIntersectPinnedMessage({ viewportPinnedIdsToAdd, viewportPinnedIdsToRemove });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheduledToUpdateViews.length) {
|
if (scheduledToUpdateViews.length) {
|
||||||
|
|||||||
@ -1,171 +1,139 @@
|
|||||||
import { useEffect, useRef, useSignal } from '../../../lib/teact/teact';
|
import { useEffect, useSignal } from '../../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../../global';
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ThreadId } from '../../../types';
|
import type { ThreadId } from '../../../types';
|
||||||
|
|
||||||
import {
|
import { selectFocusedMessageId, selectListedIds, selectOutlyingListByMessageId } from '../../../global/selectors';
|
||||||
selectFocusedMessageId,
|
|
||||||
selectListedIds,
|
|
||||||
selectOutlyingListByMessageId,
|
|
||||||
} from '../../../global/selectors';
|
|
||||||
import cycleRestrict from '../../../util/cycleRestrict';
|
import cycleRestrict from '../../../util/cycleRestrict';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import { clamp } from '../../../util/math';
|
|
||||||
|
|
||||||
|
import useDerivedSignal from '../../../hooks/useDerivedSignal';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
type PinnedIntersectionChangedParams = {
|
export type OnIntersectPinnedMessage = (params: {
|
||||||
viewportPinnedIdsToAdd?: number[];
|
viewportPinnedIdsToAdd?: number[];
|
||||||
viewportPinnedIdsToRemove?: number[];
|
viewportPinnedIdsToRemove?: number[];
|
||||||
isReversed?: boolean;
|
shouldCancelWaiting?: boolean;
|
||||||
hasScrolled?: boolean;
|
}) => void;
|
||||||
isUnmount?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PinnedIntersectionChangedCallback = (params: PinnedIntersectionChangedParams) => void;
|
let viewportPinnedIds: number[] | undefined;
|
||||||
|
let lastFocusedId: number | undefined;
|
||||||
|
|
||||||
export default function usePinnedMessage(
|
export default function usePinnedMessage(
|
||||||
chatId?: string, threadId?: ThreadId, pinnedIds?: number[], topMessageId?: number,
|
chatId?: string, threadId?: ThreadId, pinnedIds?: number[],
|
||||||
) {
|
) {
|
||||||
const [getCurrentPinnedIndexes, setCurrentPinnedIndexes] = useSignal<Record<string, number>>({});
|
const [getPinnedIndexByKey, setPinnedIndexByKey] = useSignal<Record<string, number>>({});
|
||||||
const [getForceNextPinnedInHeader, setForceNextPinnedInHeader] = useSignal<boolean | undefined>();
|
|
||||||
const viewportPinnedIdsRef = useRef<number[] | undefined>();
|
|
||||||
const [getLoadingPinnedId, setLoadingPinnedId] = useSignal<number | undefined>();
|
const [getLoadingPinnedId, setLoadingPinnedId] = useSignal<number | undefined>();
|
||||||
|
|
||||||
const key = chatId ? `${chatId}_${threadId}` : undefined;
|
const key = chatId ? `${chatId}_${threadId}` : undefined;
|
||||||
|
const getCurrentPinnedIndex = useDerivedSignal(
|
||||||
|
() => (getPinnedIndexByKey()[key!] ?? 0),
|
||||||
|
[getPinnedIndexByKey, key],
|
||||||
|
);
|
||||||
|
|
||||||
// Reset when switching chat
|
// Reset when switching chat
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setForceNextPinnedInHeader(undefined);
|
viewportPinnedIds = undefined;
|
||||||
viewportPinnedIdsRef.current = undefined;
|
|
||||||
setLoadingPinnedId(undefined);
|
setLoadingPinnedId(undefined);
|
||||||
}, [
|
}, [
|
||||||
chatId, setCurrentPinnedIndexes, setForceNextPinnedInHeader, setLoadingPinnedId, threadId,
|
chatId, setPinnedIndexByKey, setLoadingPinnedId, threadId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
const currentPinnedIndex = getCurrentPinnedIndexes()[key];
|
const currentPinnedIndex = getPinnedIndexByKey()[key];
|
||||||
const pinnedLength = pinnedIds?.length || 0;
|
const pinnedLength = pinnedIds?.length || 0;
|
||||||
if (currentPinnedIndex >= pinnedLength) {
|
if (currentPinnedIndex >= pinnedLength) {
|
||||||
setCurrentPinnedIndexes({
|
setPinnedIndexByKey({
|
||||||
...getCurrentPinnedIndexes(),
|
...getPinnedIndexByKey(),
|
||||||
[key]: Math.max(0, pinnedLength - 1),
|
[key]: Math.max(0, pinnedLength - 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [getCurrentPinnedIndexes, key, pinnedIds?.length, setCurrentPinnedIndexes]);
|
}, [getPinnedIndexByKey, key, pinnedIds?.length, setPinnedIndexByKey]);
|
||||||
|
|
||||||
const onIntersectionChanged = useLastCallback(({
|
const handleIntersectPinnedMessage: OnIntersectPinnedMessage = useLastCallback(({
|
||||||
viewportPinnedIdsToAdd = [], viewportPinnedIdsToRemove = [], isReversed, hasScrolled, isUnmount,
|
viewportPinnedIdsToAdd = [],
|
||||||
}: PinnedIntersectionChangedParams) => {
|
viewportPinnedIdsToRemove = [],
|
||||||
if (!chatId || !threadId || !key) return;
|
shouldCancelWaiting,
|
||||||
|
}) => {
|
||||||
|
if (!chatId || !threadId || !key || !pinnedIds?.length) return;
|
||||||
|
|
||||||
const global = getGlobal();
|
if (shouldCancelWaiting) {
|
||||||
|
lastFocusedId = undefined;
|
||||||
|
setLoadingPinnedId(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pinnedMessagesCount = pinnedIds?.length || 0;
|
const loadingPinnedId = getLoadingPinnedId();
|
||||||
|
if (loadingPinnedId) {
|
||||||
if (!pinnedMessagesCount || !pinnedIds) return;
|
const newPinnedIndex = pinnedIds.indexOf(loadingPinnedId);
|
||||||
|
setPinnedIndexByKey({
|
||||||
const waitingForPinnedId = getLoadingPinnedId();
|
...getPinnedIndexByKey(),
|
||||||
if (waitingForPinnedId && !hasScrolled) {
|
|
||||||
const newPinnedIndex = pinnedIds.indexOf(waitingForPinnedId);
|
|
||||||
setCurrentPinnedIndexes({
|
|
||||||
...getCurrentPinnedIndexes(),
|
|
||||||
[key]: newPinnedIndex,
|
[key]: newPinnedIndex,
|
||||||
});
|
});
|
||||||
setLoadingPinnedId(undefined);
|
setLoadingPinnedId(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasScrolled) {
|
viewportPinnedIds = unique(
|
||||||
setForceNextPinnedInHeader(undefined);
|
(viewportPinnedIds?.filter((id) => !viewportPinnedIdsToRemove.includes(id)) ?? [])
|
||||||
setLoadingPinnedId(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const forceNextPinnedInHeader = getForceNextPinnedInHeader();
|
|
||||||
|
|
||||||
const currentViewportPinnedIds = viewportPinnedIdsRef.current;
|
|
||||||
|
|
||||||
// Unmounting the Message component will fire this action, and if we've already marked the pin as
|
|
||||||
// outside the viewport, we don't need to do anything
|
|
||||||
if (isUnmount
|
|
||||||
&& viewportPinnedIdsToAdd.length === 0 && viewportPinnedIdsToRemove.length === 1
|
|
||||||
&& !currentViewportPinnedIds?.includes(viewportPinnedIdsToRemove[0])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPinnedViewportIds = unique(
|
|
||||||
(currentViewportPinnedIds?.filter((id) => !viewportPinnedIdsToRemove.includes(id)) || [])
|
|
||||||
.concat(viewportPinnedIdsToAdd),
|
.concat(viewportPinnedIdsToAdd),
|
||||||
);
|
);
|
||||||
|
|
||||||
viewportPinnedIdsRef.current = newPinnedViewportIds;
|
// Sometimes this callback is called after focus has been reset in global, so we leverage `lastFocusedId`
|
||||||
|
const focusedMessageId = selectFocusedMessageId(getGlobal(), chatId) || lastFocusedId;
|
||||||
|
|
||||||
const focusedMessageId = selectFocusedMessageId(global, chatId);
|
if (lastFocusedId && viewportPinnedIds.includes(lastFocusedId)) {
|
||||||
// Focused to some non-pinned message
|
lastFocusedId = undefined;
|
||||||
if (!newPinnedViewportIds.length && isUnmount && focusedMessageId && !pinnedIds.includes(focusedMessageId)) {
|
|
||||||
const firstPinnedIdAfterFocused = pinnedIds.find((id) => id < focusedMessageId);
|
|
||||||
if (firstPinnedIdAfterFocused) {
|
|
||||||
const newIndex = pinnedIds.indexOf(firstPinnedIdAfterFocused);
|
|
||||||
setCurrentPinnedIndexes({
|
|
||||||
...getCurrentPinnedIndexes(),
|
|
||||||
[key]: newIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceNextPinnedInHeader || isUnmount) {
|
if (focusedMessageId) {
|
||||||
|
const pinnedIndexAboveFocused = pinnedIds.findIndex((id) => id < focusedMessageId);
|
||||||
|
const newIndex = pinnedIndexAboveFocused !== -1 ? pinnedIndexAboveFocused : 0;
|
||||||
|
|
||||||
|
setPinnedIndexByKey({
|
||||||
|
...getPinnedIndexByKey(),
|
||||||
|
[key]: newIndex,
|
||||||
|
});
|
||||||
|
} else if (viewportPinnedIds.length) {
|
||||||
|
const maxViewportPinnedId = Math.max(...viewportPinnedIds);
|
||||||
|
const newIndex = pinnedIds.indexOf(maxViewportPinnedId);
|
||||||
|
|
||||||
|
setPinnedIndexByKey({
|
||||||
|
...getPinnedIndexByKey(),
|
||||||
|
[key]: newIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFocusPinnedMessage = useLastCallback((messageId: number) => {
|
||||||
|
// Focusing on a post in comments
|
||||||
|
if (!chatId || !threadId || !pinnedIds?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxId = Math.max(...newPinnedViewportIds);
|
lastFocusedId = messageId;
|
||||||
const maxIdIndex = pinnedIds.findIndex((id) => id === maxId);
|
|
||||||
const delta = isReversed ? 0 : 1;
|
|
||||||
const newIndex = newPinnedViewportIds.length ? maxIdIndex : (
|
|
||||||
currentViewportPinnedIds?.length
|
|
||||||
? clamp(pinnedIds.indexOf(currentViewportPinnedIds[0]) + delta, 0, pinnedIds.length - 1)
|
|
||||||
: 0
|
|
||||||
);
|
|
||||||
|
|
||||||
setCurrentPinnedIndexes({
|
|
||||||
...getCurrentPinnedIndexes(),
|
|
||||||
[key]: newIndex,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const onFocusPinnedMessage = useLastCallback((messageId: number): boolean => {
|
|
||||||
if (!chatId || !threadId || !key || getLoadingPinnedId()) return false;
|
|
||||||
|
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
if (!pinnedIds?.length) {
|
|
||||||
// Focusing on a post in comments
|
|
||||||
return topMessageId === messageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = pinnedIds.indexOf(messageId);
|
|
||||||
const newPinnedIndex = cycleRestrict(pinnedIds.length, index + 1);
|
|
||||||
setForceNextPinnedInHeader(true);
|
|
||||||
|
|
||||||
const listedIds = selectListedIds(global, chatId, threadId);
|
const listedIds = selectListedIds(global, chatId, threadId);
|
||||||
const isMessageLoaded = listedIds?.includes(messageId)
|
const isMessageLoaded = listedIds?.includes(messageId)
|
||||||
|| selectOutlyingListByMessageId(global, chatId, threadId, messageId);
|
|| selectOutlyingListByMessageId(global, chatId, threadId, messageId);
|
||||||
|
|
||||||
|
const currentIndex = pinnedIds.indexOf(messageId);
|
||||||
|
const newIndex = cycleRestrict(pinnedIds.length, currentIndex + 1);
|
||||||
|
|
||||||
if (isMessageLoaded) {
|
if (isMessageLoaded) {
|
||||||
setCurrentPinnedIndexes({
|
setPinnedIndexByKey({
|
||||||
...getCurrentPinnedIndexes(),
|
...getPinnedIndexByKey(),
|
||||||
[key]: newPinnedIndex,
|
[key!]: newIndex,
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
setLoadingPinnedId(pinnedIds[newPinnedIndex]);
|
setLoadingPinnedId(pinnedIds[newIndex]);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onIntersectionChanged,
|
handleIntersectPinnedMessage,
|
||||||
onFocusPinnedMessage,
|
handleFocusPinnedMessage,
|
||||||
getCurrentPinnedIndexes,
|
getCurrentPinnedIndex,
|
||||||
getLoadingPinnedId,
|
getLoadingPinnedId,
|
||||||
getForceNextPinnedInHeader,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
beginHeavyAnimation,
|
beginHeavyAnimation,
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useUnmountCleanup,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -27,7 +33,7 @@ import type {
|
|||||||
FocusDirection, IAlbum, ISettings, ScrollTargetPosition, ThreadId,
|
FocusDirection, IAlbum, ISettings, ScrollTargetPosition, ThreadId,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage';
|
import type { OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
import { AudioOrigin } from '../../../types';
|
import { AudioOrigin } from '../../../types';
|
||||||
|
|
||||||
@ -206,7 +212,7 @@ type OwnProps =
|
|||||||
isJustAdded: boolean;
|
isJustAdded: boolean;
|
||||||
memoFirstUnreadIdRef: { current: number | undefined };
|
memoFirstUnreadIdRef: { current: number | undefined };
|
||||||
getIsMessageListReady: Signal<boolean>;
|
getIsMessageListReady: Signal<boolean>;
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||||
}
|
}
|
||||||
& MessagePositionProperties;
|
& MessagePositionProperties;
|
||||||
|
|
||||||
@ -408,7 +414,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
canTranscribeVoice,
|
canTranscribeVoice,
|
||||||
viaBusinessBot,
|
viaBusinessBot,
|
||||||
effect,
|
effect,
|
||||||
onPinnedIntersectionChange,
|
onIntersectPinnedMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleMessageSelection,
|
toggleMessageSelection,
|
||||||
@ -479,14 +485,12 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
|
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
useEffect(() => {
|
useUnmountCleanup(() => {
|
||||||
if (!isPinned) return undefined;
|
if (message.isPinned) {
|
||||||
const id = album ? album.mainMessage.id : messageId;
|
const id = album ? album.mainMessage.id : messageId;
|
||||||
|
onIntersectPinnedMessage({ viewportPinnedIdsToRemove: [id] });
|
||||||
return () => {
|
}
|
||||||
onPinnedIntersectionChange({ viewportPinnedIdsToRemove: [id], isUnmount: true });
|
});
|
||||||
};
|
|
||||||
}, [album, isPinned, messageId, onPinnedIntersectionChange]);
|
|
||||||
|
|
||||||
const isLocal = isMessageLocal(message);
|
const isLocal = isMessageLocal(message);
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
@ -1050,7 +1054,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
noMediaCorners && 'no-media-corners',
|
noMediaCorners && 'no-media-corners',
|
||||||
);
|
);
|
||||||
const hasCustomAppendix = isLastInGroup
|
const hasCustomAppendix = isLastInGroup
|
||||||
&& (!hasText || (isInvertedMedia && !hasFactCheck && !hasReactions)) && !asForwarded && !withCommentButton;
|
&& (!hasText || (isInvertedMedia && !hasFactCheck && !hasReactions)) && !asForwarded && !withCommentButton;
|
||||||
const textContentClass = buildClassName(
|
const textContentClass = buildClassName(
|
||||||
'text-content',
|
'text-content',
|
||||||
'clearfix',
|
'clearfix',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user