Introduce Quick Preview (#6298)
This commit is contained in:
parent
729ed3dd7d
commit
2b7fcbb240
@ -2281,3 +2281,4 @@
|
||||
"GiftValueForSaleOnFragment" = "for sale on Fragment";
|
||||
"GiftValueForSaleOnTelegram" = "for sale on Telegram";
|
||||
"EmbeddedMessageNoCaption" = "Caption removed";
|
||||
"QuickPreview" = "Quick Preview";
|
||||
|
||||
@ -104,3 +104,4 @@ export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/
|
||||
export { default as WebAppsCloseConfirmationModal } from '../components/main/WebAppsCloseConfirmationModal';
|
||||
export { default as FrozenAccountModal } from '../components/modals/frozenAccount/FrozenAccountModal';
|
||||
export { default as ProfileRatingModal } from '../components/modals/profileRating/ProfileRatingModal';
|
||||
export { default as QuickPreviewModal } from '../components/modals/quickPreview/QuickPreviewModal';
|
||||
|
||||
@ -186,6 +186,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
reportMessages,
|
||||
openFrozenAccountModal,
|
||||
updateChatMutedState,
|
||||
openQuickPreview,
|
||||
} = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
@ -239,7 +240,13 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
const handleClick = useLastCallback((e: React.MouseEvent) => {
|
||||
if (e.altKey && !isSavedDialog && !isForum && !isPreview) {
|
||||
e.preventDefault();
|
||||
openQuickPreview({ id: chatId });
|
||||
return;
|
||||
}
|
||||
|
||||
const noForumTopicPanel = isMobile && isForumAsMessages;
|
||||
|
||||
if (isMobile) {
|
||||
|
||||
@ -103,6 +103,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
||||
focusLastMessage,
|
||||
setViewForumAsMessages,
|
||||
updateTopicMutedState,
|
||||
openQuickPreview,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
@ -153,7 +154,13 @@ const Topic: FC<OwnProps & StateProps> = ({
|
||||
orderDiff,
|
||||
});
|
||||
|
||||
const handleOpenTopic = useLastCallback(() => {
|
||||
const handleOpenTopic = useLastCallback((e: React.MouseEvent) => {
|
||||
if (e.altKey) {
|
||||
e.preventDefault();
|
||||
openQuickPreview({ id: chatId, threadId: topic.id });
|
||||
return;
|
||||
}
|
||||
|
||||
openThread({ chatId, threadId: topic.id, shouldReplaceHistory: true });
|
||||
setViewForumAsMessages({ chatId, isEnabled: false });
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers'
|
||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
|
||||
import { compact } from '../../../../util/iteratees';
|
||||
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useOldLang from '../../../../hooks/useOldLang';
|
||||
|
||||
export default function useTopicContextActions({
|
||||
@ -29,7 +30,8 @@ export default function useTopicContextActions({
|
||||
handleMute?: NoneToVoidFunction;
|
||||
handleUnmute?: NoneToVoidFunction;
|
||||
}) {
|
||||
const lang = useOldLang();
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
return useMemo(() => {
|
||||
const {
|
||||
@ -43,6 +45,7 @@ export default function useTopicContextActions({
|
||||
toggleTopicPinned,
|
||||
markTopicRead,
|
||||
openChatInNewTab,
|
||||
openQuickPreview,
|
||||
} = getActions();
|
||||
|
||||
const canToggleClosed = getCanManageTopic(chat, topic);
|
||||
@ -56,9 +59,17 @@ export default function useTopicContextActions({
|
||||
},
|
||||
};
|
||||
|
||||
const actionQuickPreview = {
|
||||
title: lang('QuickPreview'),
|
||||
icon: 'eye-outline',
|
||||
handler: () => {
|
||||
openQuickPreview({ id: chatId, threadId: topicId });
|
||||
},
|
||||
};
|
||||
|
||||
const actionUnreadMark = topic.unreadCount || !wasOpened
|
||||
? {
|
||||
title: lang('MarkAsRead'),
|
||||
title: oldLang('MarkAsRead'),
|
||||
icon: 'readchats',
|
||||
handler: () => {
|
||||
markTopicRead({ chatId, topicId });
|
||||
@ -68,42 +79,42 @@ export default function useTopicContextActions({
|
||||
|
||||
const actionPin = canTogglePinned ? (isPinned
|
||||
? {
|
||||
title: lang('UnpinFromTop'),
|
||||
title: oldLang('UnpinFromTop'),
|
||||
icon: 'unpin',
|
||||
handler: () => toggleTopicPinned({ chatId, topicId, isPinned: false }),
|
||||
}
|
||||
: {
|
||||
title: lang('PinToTop'),
|
||||
title: oldLang('PinToTop'),
|
||||
icon: 'pin',
|
||||
handler: () => toggleTopicPinned({ chatId, topicId, isPinned: true }),
|
||||
}) : undefined;
|
||||
|
||||
const actionMute = ((isChatMuted && notifySettings.mutedUntil === undefined) || notifySettings.mutedUntil)
|
||||
? {
|
||||
title: lang('ChatList.Unmute'),
|
||||
title: oldLang('ChatList.Unmute'),
|
||||
icon: 'unmute',
|
||||
handler: handleUnmute,
|
||||
}
|
||||
: {
|
||||
title: `${lang('ChatList.Mute')}...`,
|
||||
title: `${oldLang('ChatList.Mute')}...`,
|
||||
icon: 'mute',
|
||||
handler: handleMute,
|
||||
};
|
||||
|
||||
const actionCloseTopic = canToggleClosed ? (isClosed
|
||||
? {
|
||||
title: lang('lng_forum_topic_reopen'),
|
||||
title: oldLang('lng_forum_topic_reopen'),
|
||||
icon: 'reopen-topic',
|
||||
handler: () => editTopic({ chatId, topicId, isClosed: false }),
|
||||
}
|
||||
: {
|
||||
title: lang('lng_forum_topic_close'),
|
||||
title: oldLang('lng_forum_topic_close'),
|
||||
icon: 'close-topic',
|
||||
handler: () => editTopic({ chatId, topicId, isClosed: true }),
|
||||
}) : undefined;
|
||||
|
||||
const actionDelete = canDelete ? {
|
||||
title: lang('lng_forum_topic_delete'),
|
||||
title: oldLang('lng_forum_topic_delete'),
|
||||
icon: 'delete',
|
||||
destructive: true,
|
||||
handler: handleDelete,
|
||||
@ -111,11 +122,12 @@ export default function useTopicContextActions({
|
||||
|
||||
return compact([
|
||||
actionOpenInNewTab,
|
||||
actionQuickPreview,
|
||||
actionPin,
|
||||
actionUnreadMark,
|
||||
actionMute,
|
||||
actionCloseTopic,
|
||||
actionDelete,
|
||||
]) as MenuItemContextAction[];
|
||||
}, [topic, chat, isChatMuted, wasOpened, lang, canDelete, handleDelete, handleMute, handleUnmute]);
|
||||
}, [topic, chat, isChatMuted, wasOpened, lang, oldLang, canDelete, handleDelete, handleMute, handleUnmute]);
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
withOpenAppButton,
|
||||
onClick,
|
||||
}) => {
|
||||
const { requestMainWebView, updateChatMutedState } = getActions();
|
||||
const { requestMainWebView, updateChatMutedState, openQuickPreview } = getActions();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [isMuteModalOpen, openMuteModal, closeMuteModal] = useFlag();
|
||||
@ -85,7 +85,12 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
handleChatFolderChange,
|
||||
}, true);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
const handleClick = useLastCallback((e: React.MouseEvent) => {
|
||||
if (e.altKey && chat && !chat.isForum) {
|
||||
e.preventDefault();
|
||||
openQuickPreview({ id: chatId });
|
||||
return;
|
||||
}
|
||||
onClick(chatId);
|
||||
});
|
||||
|
||||
@ -100,7 +105,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
const buttonRef = useSelectWithEnter(handleClick);
|
||||
const buttonRef = useSelectWithEnter(() => {
|
||||
onClick(chatId);
|
||||
});
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../global';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiTopic } from '../../../api/types';
|
||||
|
||||
@ -26,15 +26,23 @@ type StateProps = {
|
||||
const TOPIC_ICON_SIZE = 2 * REM;
|
||||
|
||||
const LeftSearchResultTopic: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
topicId,
|
||||
topic,
|
||||
onClick,
|
||||
}) => {
|
||||
const handleClick = useCallback(() => {
|
||||
onClick(topicId);
|
||||
}, [topicId, onClick]);
|
||||
const { openQuickPreview } = getActions();
|
||||
|
||||
const buttonRef = useSelectWithEnter(handleClick);
|
||||
const handleClick = useCallback((e: React.MouseEvent) => {
|
||||
if (e.altKey) {
|
||||
e.preventDefault();
|
||||
openQuickPreview({ id: chatId, threadId: topicId });
|
||||
return;
|
||||
}
|
||||
onClick(topicId);
|
||||
}, [chatId, topicId, onClick, openQuickPreview]);
|
||||
|
||||
const buttonRef = useSelectWithEnter(() => onClick(topicId));
|
||||
|
||||
if (!topic) {
|
||||
return undefined;
|
||||
|
||||
@ -96,9 +96,10 @@ type OwnProps = {
|
||||
withDefaultBg: boolean;
|
||||
isContactRequirePremium?: boolean;
|
||||
paidMessagesStars?: number;
|
||||
onScrollDownToggle: BooleanToVoidFunction;
|
||||
onNotchToggle: BooleanToVoidFunction;
|
||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||
isQuickPreview?: boolean;
|
||||
onScrollDownToggle?: BooleanToVoidFunction;
|
||||
onNotchToggle?: AnyToVoidFunction;
|
||||
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -224,6 +225,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
canTranslate,
|
||||
translationLanguage,
|
||||
shouldAutoTranslate,
|
||||
isQuickPreview,
|
||||
onIntersectPinnedMessage,
|
||||
onScrollDownToggle,
|
||||
onNotchToggle,
|
||||
@ -443,7 +445,12 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return debounce(() => loadViewportMessages({ direction: LoadMoreDirection.Around }), 1000, true, false);
|
||||
return debounce(
|
||||
() => loadViewportMessages({ direction: LoadMoreDirection.Around, chatId, threadId }),
|
||||
1000,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
}, [loadViewportMessages, messageIds]);
|
||||
|
||||
@ -469,7 +476,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const isFocusing = Boolean(selectTabState(global).focusedMessage?.chatId);
|
||||
if (!isFocusing) {
|
||||
onIntersectPinnedMessage({ shouldCancelWaiting: true });
|
||||
onIntersectPinnedMessage?.({ shouldCancelWaiting: true });
|
||||
}
|
||||
|
||||
if (!container.parentElement) {
|
||||
@ -478,7 +485,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
|
||||
scrollOffsetRef.current = container.scrollHeight - container.scrollTop;
|
||||
|
||||
if (type === 'thread') {
|
||||
if (type === 'thread' && !isQuickPreview) {
|
||||
setScrollOffset({ chatId, threadId, scrollOffset: scrollOffsetRef.current });
|
||||
}
|
||||
});
|
||||
@ -717,7 +724,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
if (hasMessages) return;
|
||||
|
||||
onScrollDownToggle(false);
|
||||
onScrollDownToggle?.(false);
|
||||
}, [hasMessages, onScrollDownToggle]);
|
||||
|
||||
const activeKey = isRestricted ? (
|
||||
@ -790,6 +797,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
nameChangeDate={nameChangeDate}
|
||||
photoChangeDate={photoChangeDate}
|
||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||
isQuickPreview={isQuickPreview}
|
||||
onScrollDownToggle={onScrollDownToggle}
|
||||
onNotchToggle={onNotchToggle}
|
||||
onIntersectPinnedMessage={onIntersectPinnedMessage}
|
||||
|
||||
@ -74,9 +74,10 @@ interface OwnProps {
|
||||
photoChangeDate?: number;
|
||||
noAppearanceAnimation: boolean;
|
||||
isSavedDialog?: boolean;
|
||||
onScrollDownToggle: BooleanToVoidFunction;
|
||||
onNotchToggle: AnyToVoidFunction;
|
||||
onIntersectPinnedMessage: OnIntersectPinnedMessage;
|
||||
isQuickPreview?: boolean;
|
||||
onScrollDownToggle?: BooleanToVoidFunction;
|
||||
onNotchToggle?: AnyToVoidFunction;
|
||||
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||
canPost?: boolean;
|
||||
}
|
||||
|
||||
@ -110,6 +111,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
photoChangeDate,
|
||||
noAppearanceAnimation,
|
||||
isSavedDialog,
|
||||
isQuickPreview,
|
||||
onScrollDownToggle,
|
||||
onNotchToggle,
|
||||
onIntersectPinnedMessage,
|
||||
@ -126,7 +128,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
observeIntersectionForReading,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef, onIntersectPinnedMessage, chatId);
|
||||
} = useMessageObservers(type, containerRef, memoFirstUnreadIdRef, onIntersectPinnedMessage, chatId, isQuickPreview);
|
||||
|
||||
const {
|
||||
withHistoryTriggers,
|
||||
|
||||
@ -102,7 +102,7 @@ import ReactorListModal from './ReactorListModal.async';
|
||||
import MiddleSearch from './search/MiddleSearch.async';
|
||||
|
||||
import './MiddleColumn.scss';
|
||||
import styles from './MiddleColumn.module.scss';
|
||||
import backgroundStyles from '../../styles/_patternBackground.module.scss';
|
||||
|
||||
interface OwnProps {
|
||||
leftColumnRef: ElementRef<HTMLDivElement>;
|
||||
@ -439,12 +439,12 @@ function MiddleColumn({
|
||||
);
|
||||
|
||||
const bgClassName = buildClassName(
|
||||
styles.background,
|
||||
withRightColumnAnimation && styles.withTransition,
|
||||
customBackground && styles.customBgImage,
|
||||
backgroundColor && styles.customBgColor,
|
||||
customBackground && isBackgroundBlurred && styles.blurred,
|
||||
isRightColumnShown && styles.withRightColumn,
|
||||
backgroundStyles.background,
|
||||
withRightColumnAnimation && backgroundStyles.withTransition,
|
||||
customBackground && backgroundStyles.customBgImage,
|
||||
backgroundColor && backgroundStyles.customBgColor,
|
||||
customBackground && isBackgroundBlurred && backgroundStyles.blurred,
|
||||
isRightColumnShown && backgroundStyles.withRightColumn,
|
||||
);
|
||||
|
||||
const messagingDisabledClassName = buildClassName(
|
||||
@ -578,7 +578,7 @@ function MiddleColumn({
|
||||
paidMessagesStars={paidMessagesStars}
|
||||
withBottomShift={withMessageListBottomShift}
|
||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||
onIntersectPinnedMessage={renderingHandleIntersectPinnedMessage!}
|
||||
onIntersectPinnedMessage={renderingHandleIntersectPinnedMessage}
|
||||
/>
|
||||
<div className={footerClassName}>
|
||||
<FloatingActionButtons
|
||||
|
||||
@ -17,8 +17,9 @@ export default function useMessageObservers(
|
||||
type: MessageListType,
|
||||
containerRef: ElementRef<HTMLDivElement>,
|
||||
memoFirstUnreadIdRef: { current: number | undefined },
|
||||
onIntersectPinnedMessage: OnIntersectPinnedMessage,
|
||||
onIntersectPinnedMessage: OnIntersectPinnedMessage | undefined,
|
||||
chatId: string,
|
||||
isQuickPreview?: boolean,
|
||||
) {
|
||||
const {
|
||||
markMessageListRead, markMentionsRead, animateUnreadReaction,
|
||||
@ -81,12 +82,18 @@ export default function useMessageObservers(
|
||||
}
|
||||
});
|
||||
|
||||
if (memoFirstUnreadIdRef.current && maxId >= memoFirstUnreadIdRef.current) {
|
||||
markMessageListRead({ maxId });
|
||||
}
|
||||
if (!isQuickPreview) {
|
||||
if (memoFirstUnreadIdRef.current && maxId >= memoFirstUnreadIdRef.current) {
|
||||
markMessageListRead({ maxId });
|
||||
}
|
||||
|
||||
if (mentionIds.length) {
|
||||
markMentionsRead({ chatId, messageIds: mentionIds });
|
||||
if (mentionIds.length) {
|
||||
markMentionsRead({ chatId, messageIds: mentionIds });
|
||||
}
|
||||
|
||||
if (scheduledToUpdateViews.length) {
|
||||
scheduleForViewsIncrement({ chatId, ids: scheduledToUpdateViews });
|
||||
}
|
||||
}
|
||||
|
||||
if (reactionIds.length) {
|
||||
@ -94,11 +101,7 @@ export default function useMessageObservers(
|
||||
}
|
||||
|
||||
if (viewportPinnedIdsToAdd.length || viewportPinnedIdsToRemove.length) {
|
||||
onIntersectPinnedMessage({ viewportPinnedIdsToAdd, viewportPinnedIdsToRemove });
|
||||
}
|
||||
|
||||
if (scheduledToUpdateViews.length) {
|
||||
scheduleForViewsIncrement({ chatId, ids: scheduledToUpdateViews });
|
||||
onIntersectPinnedMessage?.({ viewportPinnedIdsToAdd, viewportPinnedIdsToRemove });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -28,8 +28,8 @@ export default function useScrollHooks(
|
||||
getContainerHeight: Signal<number | undefined>,
|
||||
isViewportNewest: boolean,
|
||||
isUnread: boolean,
|
||||
onScrollDownToggle: BooleanToVoidFunction,
|
||||
onNotchToggle: BooleanToVoidFunction,
|
||||
onScrollDownToggle: BooleanToVoidFunction | undefined,
|
||||
onNotchToggle: AnyToVoidFunction | undefined,
|
||||
isReady: boolean,
|
||||
) {
|
||||
const { loadViewportMessages } = getActions();
|
||||
@ -51,14 +51,16 @@ export default function useScrollHooks(
|
||||
if (!isReady) return;
|
||||
|
||||
if (!messageIds?.length) {
|
||||
onScrollDownToggle(false);
|
||||
onNotchToggle(false);
|
||||
onScrollDownToggle?.(false);
|
||||
onNotchToggle?.(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isViewportNewest) {
|
||||
onScrollDownToggle(true);
|
||||
onNotchToggle(true);
|
||||
onScrollDownToggle?.(true);
|
||||
onNotchToggle?.(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -74,8 +76,8 @@ export default function useScrollHooks(
|
||||
|
||||
if (scrollHeight === 0) return;
|
||||
|
||||
onScrollDownToggle(isUnread ? !isAtBottom : !isNearBottom);
|
||||
onNotchToggle(!isAtBottom);
|
||||
onScrollDownToggle?.(isUnread ? !isAtBottom : !isNearBottom);
|
||||
onNotchToggle?.(!isAtBottom);
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type React from '../../lib/teact/teact';
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
@ -37,6 +37,7 @@ import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
||||
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
|
||||
import PriceConfirmModal from './priceConfirm/PriceConfirmModal.async';
|
||||
import ProfileRatingModal from './profileRating/ProfileRatingModal.async';
|
||||
import QuickPreviewModal from './quickPreview/QuickPreviewModal.async';
|
||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||
import ReportModal from './reportModal/ReportModal.async';
|
||||
import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async';
|
||||
@ -97,14 +98,15 @@ type ModalKey = keyof Pick<TabState,
|
||||
'isFrozenAccountModalOpen' |
|
||||
'deleteAccountModal' |
|
||||
'isAgeVerificationModalOpen' |
|
||||
'profileRatingModal'
|
||||
'profileRatingModal' |
|
||||
'quickPreview'
|
||||
>;
|
||||
|
||||
type StateProps = {
|
||||
[K in ModalKey]?: TabState[K];
|
||||
};
|
||||
type ModalRegistry = {
|
||||
[K in ModalKey]: React.FC<{
|
||||
[K in ModalKey]: FC<{
|
||||
modal: TabState[K];
|
||||
}>;
|
||||
};
|
||||
@ -157,6 +159,7 @@ const MODALS: ModalRegistry = {
|
||||
deleteAccountModal: DeleteAccountModal,
|
||||
isAgeVerificationModalOpen: AgeVerificationModal,
|
||||
profileRatingModal: ProfileRatingModal,
|
||||
quickPreview: QuickPreviewModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './QuickPreviewModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const QuickPreviewModalAsync: FC<OwnProps> = memo((props) => {
|
||||
const { modal } = props;
|
||||
|
||||
const QuickPreviewModal = useModuleLoader(Bundles.Extra, 'QuickPreviewModal', !modal);
|
||||
|
||||
return QuickPreviewModal ? <QuickPreviewModal {...props} /> : undefined;
|
||||
});
|
||||
|
||||
export default QuickPreviewModalAsync;
|
||||
@ -0,0 +1,55 @@
|
||||
.root {
|
||||
:global(.modal-dialog) {
|
||||
overflow: hidden;
|
||||
height: 40rem;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
|
||||
:global(.messages-container) {
|
||||
pointer-events: none;
|
||||
|
||||
// Prevent right column width calculation from applying
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
:global(.sticky-date span) {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Analogue of `#MiddleColumn` from MiddleColumn.module.scss
|
||||
.column {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Analogue of `.messages-layout` from MiddleColumn.module.scss
|
||||
.messagesLayout {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
133
src/components/modals/quickPreview/QuickPreviewModal.tsx
Normal file
133
src/components/modals/quickPreview/QuickPreviewModal.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import type { FC } from '@teact';
|
||||
import { memo, useEffect } from '@teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { TabState } from '../../../global/types';
|
||||
import type { ThemeKey } from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { selectTheme, selectThemeValues } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
|
||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||
import useCustomBackground from '../../../hooks/useCustomBackground';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import MessageList from '../../middle/MessageList';
|
||||
import Modal from '../../ui/Modal';
|
||||
import QuickPreviewModalHeader from './QuickPreviewModalHeader';
|
||||
|
||||
import backgroundStyles from '../../../styles/_patternBackground.module.scss';
|
||||
import styles from './QuickPreviewModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['quickPreview'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
theme: ThemeKey;
|
||||
customBackground?: string;
|
||||
backgroundColor?: string;
|
||||
patternColor?: string;
|
||||
isBackgroundBlurred?: boolean;
|
||||
};
|
||||
|
||||
const QuickPreviewModal: FC<OwnProps & StateProps> = ({
|
||||
modal,
|
||||
theme,
|
||||
customBackground,
|
||||
backgroundColor,
|
||||
patternColor,
|
||||
isBackgroundBlurred,
|
||||
}) => {
|
||||
const { closeQuickPreview, openChat, openThread } = getActions();
|
||||
|
||||
const chatId = modal?.chatId;
|
||||
const threadId = modal?.threadId;
|
||||
const isOpen = Boolean(chatId);
|
||||
const customBackgroundValue = useCustomBackground(theme, customBackground);
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeQuickPreview();
|
||||
});
|
||||
|
||||
const handleContentClick = useLastCallback(() => {
|
||||
if (chatId) {
|
||||
if (threadId) {
|
||||
openThread({ chatId, threadId, shouldReplaceHistory: true });
|
||||
} else {
|
||||
openChat({ id: chatId, shouldReplaceHistory: true });
|
||||
}
|
||||
closeQuickPreview();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => isOpen ? captureEscKeyListener(handleClose) : undefined, [isOpen, handleClose]);
|
||||
|
||||
useHistoryBack({
|
||||
isActive: isOpen,
|
||||
onBack: handleClose,
|
||||
});
|
||||
|
||||
const { chatId: renderingChatId, threadId: renderingThreadId } = useCurrentOrPrev(modal, true)!;
|
||||
|
||||
const bgClassName = buildClassName(
|
||||
backgroundStyles.background,
|
||||
customBackground && backgroundStyles.customBgImage,
|
||||
backgroundColor && backgroundStyles.customBgColor,
|
||||
customBackground && isBackgroundBlurred && backgroundStyles.blurred,
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
header={<QuickPreviewModalHeader chatId={renderingChatId} threadId={renderingThreadId} onClose={handleClose} />}
|
||||
className={styles.root}
|
||||
contentClassName={styles.content}
|
||||
>
|
||||
<div
|
||||
className={styles.column}
|
||||
style={buildStyle(
|
||||
`--pattern-color: ${patternColor}`,
|
||||
backgroundColor && `--theme-background-color: ${backgroundColor}`,
|
||||
)}
|
||||
onClick={handleContentClick}
|
||||
>
|
||||
<div
|
||||
className={bgClassName}
|
||||
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
|
||||
/>
|
||||
<div className={styles.messagesLayout}>
|
||||
<MessageList
|
||||
chatId={renderingChatId}
|
||||
threadId={renderingThreadId || MAIN_THREAD_ID}
|
||||
type="thread"
|
||||
canPost={false}
|
||||
isReady
|
||||
withDefaultBg={Boolean(!customBackground && !backgroundColor)}
|
||||
isQuickPreview
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global, { modal: chatId }): Complete<StateProps> => {
|
||||
const theme = selectTheme(global);
|
||||
const {
|
||||
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
|
||||
} = selectThemeValues(global, theme) || {};
|
||||
|
||||
return {
|
||||
theme,
|
||||
customBackground,
|
||||
backgroundColor,
|
||||
patternColor,
|
||||
isBackgroundBlurred,
|
||||
};
|
||||
})(QuickPreviewModal));
|
||||
@ -0,0 +1,98 @@
|
||||
.root {
|
||||
position: relative;
|
||||
min-height: 3rem;
|
||||
padding: 0.5rem 0.625rem 0.625rem 0.75rem !important;
|
||||
border-bottom: 1px solid var(--color-borders);
|
||||
|
||||
:global(.modal-title) {
|
||||
margin: 0 !important;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton, .markAsReadButton {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute !important;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.markAsReadButton {
|
||||
position: absolute !important;
|
||||
top: 0.5rem;
|
||||
right: 3rem;
|
||||
}
|
||||
|
||||
.chatInfoOverride {
|
||||
// Mimic left column chat list styling
|
||||
:global(.ChatInfo) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 0.625rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:global(.Avatar) {
|
||||
flex-shrink: 0;
|
||||
width: 2.625rem;
|
||||
height: 2.625rem;
|
||||
}
|
||||
|
||||
:global(.info) {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:global(.title),
|
||||
:global(.fullName) {
|
||||
overflow: hidden;
|
||||
|
||||
margin-bottom: 0;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1.25rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:global(.status) {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.125rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
:global(.topic-header-icon) {
|
||||
--custom-emoji-size: 2.25rem;
|
||||
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
font-size: 2.25rem;
|
||||
|
||||
:global(.emoji-small) {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
:global(.topic-icon-letter) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
&:global(.general-forum-icon) {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.Transition.message-count-transition) {
|
||||
height: 1.125rem;
|
||||
}
|
||||
}
|
||||
157
src/components/modals/quickPreview/QuickPreviewModalHeader.tsx
Normal file
157
src/components/modals/quickPreview/QuickPreviewModalHeader.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import type { FC } from '@teact';
|
||||
import { memo } from '@teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiTypingStatus, ApiUpdateConnectionStateType } from '../../../api/types';
|
||||
import type { ThreadId } from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { getIsSavedDialog } from '../../../global/helpers';
|
||||
import { selectChat, selectThreadParam, selectTopic } from '../../../global/selectors';
|
||||
import { isUserId } from '../../../util/entities/ids';
|
||||
|
||||
import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import Button from '../../ui/Button';
|
||||
|
||||
import styles from './QuickPreviewModalHeader.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
threadId?: ThreadId;
|
||||
onClose: VoidFunction;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
connectionState?: ApiUpdateConnectionStateType;
|
||||
isSyncing?: boolean;
|
||||
isFetchingDifference?: boolean;
|
||||
typingStatus?: ApiTypingStatus;
|
||||
isSavedDialog?: boolean;
|
||||
unreadCount?: number;
|
||||
hasUnreadMark?: boolean;
|
||||
};
|
||||
|
||||
const EMOJI_STATUS_SIZE = 20;
|
||||
|
||||
const QuickPreviewModalHeader: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
threadId,
|
||||
chat,
|
||||
connectionState,
|
||||
isSyncing,
|
||||
isFetchingDifference,
|
||||
typingStatus,
|
||||
isSavedDialog,
|
||||
unreadCount,
|
||||
hasUnreadMark,
|
||||
onClose,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
const { markChatMessagesRead } = getActions();
|
||||
const {
|
||||
connectionStatusText,
|
||||
} = useConnectionStatus(oldLang, connectionState, isSyncing || isFetchingDifference, true);
|
||||
|
||||
const handleMarkAsRead = useLastCallback(() => {
|
||||
markChatMessagesRead({ id: chatId });
|
||||
});
|
||||
|
||||
const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined;
|
||||
const realChatId = isSavedDialog ? String(MAIN_THREAD_ID) : chatId;
|
||||
const displayChatId = chat?.isMonoforum ? chat.linkedMonoforumId! : realChatId;
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
{Boolean(unreadCount || hasUnreadMark) && (
|
||||
<Button
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang('ChatListContextMaskAsRead')}
|
||||
onClick={handleMarkAsRead}
|
||||
className={styles.markAsReadButton}
|
||||
>
|
||||
<Icon name="readchats" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={onClose}
|
||||
className={styles.closeButton}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<div className="modal-title">
|
||||
<div className={styles.chatInfoOverride}>
|
||||
{isUserId(displayChatId) ? (
|
||||
<PrivateChatInfo
|
||||
key={displayChatId}
|
||||
userId={displayChatId}
|
||||
typingStatus={typingStatus}
|
||||
status={connectionStatusText || savedMessagesStatus}
|
||||
withDots={Boolean(connectionStatusText)}
|
||||
withFullInfo={false}
|
||||
withMediaViewer={false}
|
||||
withStory={false}
|
||||
withUpdatingStatus
|
||||
isSavedDialog={isSavedDialog}
|
||||
emojiStatusSize={EMOJI_STATUS_SIZE}
|
||||
noRtl
|
||||
/>
|
||||
) : (
|
||||
<GroupChatInfo
|
||||
key={displayChatId}
|
||||
chatId={displayChatId}
|
||||
threadId={!isSavedDialog ? threadId : undefined}
|
||||
typingStatus={typingStatus}
|
||||
withMonoforumStatus={chat?.isMonoforum}
|
||||
status={connectionStatusText || savedMessagesStatus}
|
||||
withDots={Boolean(connectionStatusText)}
|
||||
withMediaViewer={false}
|
||||
withFullInfo={false}
|
||||
withUpdatingStatus
|
||||
withStory={false}
|
||||
isSavedDialog={isSavedDialog}
|
||||
emojiStatusSize={EMOJI_STATUS_SIZE}
|
||||
noRtl
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId }): Complete<StateProps> => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const typingStatus = selectThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'typingStatus');
|
||||
const isSavedDialog = getIsSavedDialog(chatId, threadId || MAIN_THREAD_ID, global.currentUserId);
|
||||
const unreadCount = chat?.isForum && threadId
|
||||
? selectTopic(global, chatId, threadId)?.unreadCount
|
||||
: chat?.unreadCount;
|
||||
|
||||
return {
|
||||
chat,
|
||||
connectionState: global.connectionState,
|
||||
isSyncing: global.isSyncing,
|
||||
isFetchingDifference: global.isFetchingDifference,
|
||||
typingStatus,
|
||||
isSavedDialog,
|
||||
unreadCount,
|
||||
hasUnreadMark: chat?.hasUnreadMark,
|
||||
};
|
||||
},
|
||||
)(QuickPreviewModalHeader));
|
||||
@ -1180,3 +1180,19 @@ addActionHandler('updateSharePreparedMessageModalSendArgs', async (global, actio
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openQuickPreview', (global, actions, payload): ActionReturnType => {
|
||||
const { id: chatId, threadId, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
quickPreview: { chatId, threadId },
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeQuickPreview', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
quickPreview: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -2509,6 +2509,12 @@ export interface ActionPayloads {
|
||||
} & WithTabId;
|
||||
closeSuggestedPostApprovalModal: WithTabId | undefined;
|
||||
|
||||
openQuickPreview: {
|
||||
id: string;
|
||||
threadId?: ThreadId;
|
||||
} & WithTabId;
|
||||
closeQuickPreview: WithTabId | undefined;
|
||||
|
||||
openDeleteMessageModal: ({
|
||||
chatId: string;
|
||||
messageIds: number[];
|
||||
|
||||
@ -875,6 +875,11 @@ export type TabState = {
|
||||
errorKey?: RegularLangFnParameters;
|
||||
};
|
||||
|
||||
quickPreview?: {
|
||||
chatId: string;
|
||||
threadId?: ThreadId;
|
||||
};
|
||||
|
||||
isWaitingForStarGiftUpgrade?: true;
|
||||
isWaitingForStarGiftTransfer?: true;
|
||||
insertingPeerIdMention?: string;
|
||||
|
||||
@ -5,9 +5,7 @@ import type { ApiChat, ApiTopic, ApiUser } from '../api/types';
|
||||
import type { MenuItemContextAction } from '../components/ui/ListItem';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../config';
|
||||
import {
|
||||
getCanDeleteChat, isChatArchived, isChatChannel, isChatGroup,
|
||||
} from '../global/helpers';
|
||||
import { getCanDeleteChat, isChatArchived, isChatChannel, isChatGroup } from '../global/helpers';
|
||||
import { IS_TAURI } from '../util/browser/globalEnvironment';
|
||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../util/browser/windowEnvironment';
|
||||
import { isUserId } from '../util/entities/ids';
|
||||
@ -86,6 +84,7 @@ const useChatContextActions = ({
|
||||
markChatMessagesRead,
|
||||
markChatUnread,
|
||||
openChatInNewTab,
|
||||
openQuickPreview,
|
||||
} = getActions();
|
||||
|
||||
const actionOpenInNewTab = IS_OPEN_IN_NEW_TAB_SUPPORTED && {
|
||||
@ -100,6 +99,16 @@ const useChatContextActions = ({
|
||||
},
|
||||
};
|
||||
|
||||
const actionQuickPreview = !isSavedDialog && !chat.isForum && {
|
||||
title: lang('QuickPreview'),
|
||||
icon: 'eye-outline',
|
||||
handler: () => {
|
||||
openQuickPreview({
|
||||
id: chat.id,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const togglePinned = () => {
|
||||
if (isSavedDialog) {
|
||||
toggleSavedDialogPinned({ id: chat.id });
|
||||
@ -128,7 +137,7 @@ const useChatContextActions = ({
|
||||
};
|
||||
|
||||
if (isSavedDialog) {
|
||||
return compact([actionOpenInNewTab, actionPin, actionDelete]) as MenuItemContextAction[];
|
||||
return compact([actionOpenInNewTab, actionQuickPreview, actionPin, actionDelete]) as MenuItemContextAction[];
|
||||
}
|
||||
|
||||
const actionAddToFolder = canChangeFolder ? {
|
||||
@ -150,12 +159,15 @@ const useChatContextActions = ({
|
||||
};
|
||||
|
||||
if (isInSearch) {
|
||||
return compact([actionOpenInNewTab, actionPin, actionAddToFolder, actionMute]) as MenuItemContextAction[];
|
||||
return compact([
|
||||
actionOpenInNewTab, actionQuickPreview, actionPin, actionAddToFolder, actionMute,
|
||||
]) as MenuItemContextAction[];
|
||||
}
|
||||
|
||||
const actionMaskAsRead = (
|
||||
chat.unreadCount || chat.hasUnreadMark || Object.values(topics || {}).some(({ unreadCount }) => unreadCount)
|
||||
) ? {
|
||||
)
|
||||
? {
|
||||
title: lang('ChatListContextMaskAsRead'),
|
||||
icon: 'readchats',
|
||||
handler: () => markChatMessagesRead({ id: chat.id }),
|
||||
@ -177,6 +189,7 @@ const useChatContextActions = ({
|
||||
|
||||
return compact([
|
||||
actionOpenInNewTab,
|
||||
actionQuickPreview,
|
||||
actionAddToFolder,
|
||||
actionMaskAsRead,
|
||||
actionMarkAsUnread,
|
||||
|
||||
@ -1,24 +1,26 @@
|
||||
import { useCallback, useEffect, useRef } from '../lib/teact/teact';
|
||||
import { useEffect, useRef } from '../lib/teact/teact';
|
||||
|
||||
import useLastCallback from './useLastCallback.ts';
|
||||
|
||||
const useSendWithEnter = (
|
||||
onSelect: NoneToVoidFunction,
|
||||
) => {
|
||||
const buttonRef = useRef<HTMLDivElement>();
|
||||
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
const handleKeyDown = useLastCallback((e: KeyboardEvent) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
const isFocused = buttonRef.current === document.activeElement;
|
||||
|
||||
if (isFocused) {
|
||||
onSelect();
|
||||
}
|
||||
}, [onSelect]);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown, false);
|
||||
|
||||
return () => window.removeEventListener('keydown', handleKeyDown, false);
|
||||
}, [handleKeyDown]);
|
||||
}, []);
|
||||
|
||||
return buttonRef;
|
||||
};
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
:global(html.theme-light) &:not(.customBgImage)::before {
|
||||
background-image: url('../../assets/chat-bg-br.png');
|
||||
background-image: url('../assets/chat-bg-br.png');
|
||||
}
|
||||
|
||||
&:not(.customBgImage).customBgColor::before {
|
||||
@ -77,14 +77,14 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
background-image: url('../../assets/chat-bg-pattern-light.png');
|
||||
background-image: url('../assets/chat-bg-pattern-light.png');
|
||||
background-repeat: repeat;
|
||||
background-position: top right;
|
||||
background-size: 510px auto;
|
||||
mix-blend-mode: overlay;
|
||||
|
||||
:global(html.theme-dark) & {
|
||||
background-image: url('../../assets/chat-bg-pattern-dark.png');
|
||||
background-image: url('../assets/chat-bg-pattern-dark.png');
|
||||
mix-blend-mode: unset;
|
||||
}
|
||||
}
|
||||
1
src/types/language.d.ts
vendored
1
src/types/language.d.ts
vendored
@ -1705,6 +1705,7 @@ export interface LangPair {
|
||||
'GiftValueForSaleOnFragment': undefined;
|
||||
'GiftValueForSaleOnTelegram': undefined;
|
||||
'EmbeddedMessageNoCaption': undefined;
|
||||
'QuickPreview': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user