Middle Header: Support bot ads (#5220)
This commit is contained in:
parent
5a647a0a83
commit
228cd56091
@ -704,7 +704,8 @@
|
|||||||
"Answer_other" = "{count} answers";
|
"Answer_other" = "{count} answers";
|
||||||
"Vote" = "Vote";
|
"Vote" = "Vote";
|
||||||
"MessageRecommendedLabel" = "recommended";
|
"MessageRecommendedLabel" = "recommended";
|
||||||
"SponsoredMessage" = "ad";
|
"SponsoredMessageAd" = "Ad";
|
||||||
|
"SponsoredMessageAdWhatIsThis" = "what's this?";
|
||||||
"PremiumStickerTooltip" = "This set contains premium stickers like this one.";
|
"PremiumStickerTooltip" = "This set contains premium stickers like this one.";
|
||||||
"ViewAction" = "View";
|
"ViewAction" = "View";
|
||||||
"Loading" = "Loading...";
|
"Loading" = "Loading...";
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCode
|
|||||||
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
||||||
export { default as ChatInviteModal } from '../components/modals/chatInvite/ChatInviteModal';
|
export { default as ChatInviteModal } from '../components/modals/chatInvite/ChatInviteModal';
|
||||||
|
|
||||||
export { default as AboutAdsModal } from '../components/common/AboutAdsModal';
|
export { default as AboutAdsModal } from '../components/modals/aboutAds/AboutAdsModal';
|
||||||
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
||||||
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
||||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||||
|
|||||||
@ -1,127 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { TableAboutData } from '../modals/common/TableAboutModal';
|
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
import renderText from './helpers/renderText';
|
|
||||||
|
|
||||||
import useSelectorSignal from '../../hooks/data/useSelectorSignal';
|
|
||||||
import useDerivedState from '../../hooks/useDerivedState';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
|
||||||
|
|
||||||
import TableAboutModal from '../modals/common/TableAboutModal';
|
|
||||||
import Button from '../ui/Button';
|
|
||||||
import Modal from '../ui/Modal';
|
|
||||||
import SafeLink from './SafeLink';
|
|
||||||
|
|
||||||
import styles from './AboutAdsModal.module.scss';
|
|
||||||
|
|
||||||
export type OwnProps = {
|
|
||||||
isOpen: boolean;
|
|
||||||
isMonetizationSharing?: boolean;
|
|
||||||
onClose: NoneToVoidFunction;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AboutAdsModal: FC<OwnProps> = ({
|
|
||||||
isOpen,
|
|
||||||
isMonetizationSharing,
|
|
||||||
onClose,
|
|
||||||
}) => {
|
|
||||||
const oldLang = useOldLang();
|
|
||||||
|
|
||||||
const minLevelSignal = useSelectorSignal((global) => global.appConfig?.channelRestrictAdsLevelMin);
|
|
||||||
const minLevelToRestrictAds = useDerivedState(minLevelSignal);
|
|
||||||
|
|
||||||
const regularAdContent = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>{oldLang('SponsoredMessageInfoScreen.Title')}</h3>
|
|
||||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription1'), ['br'])}</p>
|
|
||||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription2'), ['br'])}</p>
|
|
||||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription3'), ['br'])}</p>
|
|
||||||
<p>
|
|
||||||
<SafeLink
|
|
||||||
url={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
|
||||||
text={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
<p>{renderText(oldLang('SponsoredMessageInfoDescription4'), ['br'])}</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}, [oldLang]);
|
|
||||||
|
|
||||||
const modalData = useMemo(() => {
|
|
||||||
if (!isOpen) return undefined;
|
|
||||||
|
|
||||||
const header = (
|
|
||||||
<>
|
|
||||||
<h3 className={styles.title}>{oldLang('AboutRevenueSharingAds')}</h3>
|
|
||||||
<p className={buildClassName(styles.description, styles.secondary)}>
|
|
||||||
{oldLang('RevenueSharingAdsAlertSubtitle')}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const listItemData = [
|
|
||||||
['lock', oldLang('RevenueSharingAdsInfo1Title'),
|
|
||||||
renderText(oldLang('RevenueSharingAdsInfo1Subtitle'), ['simple_markdown'])],
|
|
||||||
['revenue-split', oldLang('RevenueSharingAdsInfo2Title'),
|
|
||||||
renderText(oldLang('RevenueSharingAdsInfo2Subtitle'), ['simple_markdown'])],
|
|
||||||
['nochannel', oldLang('RevenueSharingAdsInfo3Title'),
|
|
||||||
renderText(oldLang('RevenueSharingAdsInfo3Subtitle', minLevelToRestrictAds), ['simple_markdown'])],
|
|
||||||
] satisfies TableAboutData;
|
|
||||||
|
|
||||||
const footer = (
|
|
||||||
<>
|
|
||||||
<h3 className={styles.title}>{renderText(oldLang('RevenueSharingAdsInfo4Title'), ['simple_markdown'])}</h3>
|
|
||||||
<p className={styles.description}>
|
|
||||||
{renderText(oldLang('RevenueSharingAdsInfo4Subtitle2', ''), ['simple_markdown'])}
|
|
||||||
<SafeLink
|
|
||||||
url={oldLang('PromoteUrl')}
|
|
||||||
text={oldLang('LearnMoreArrow')}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
header,
|
|
||||||
listItemData,
|
|
||||||
footer,
|
|
||||||
};
|
|
||||||
}, [isOpen, oldLang, minLevelToRestrictAds]);
|
|
||||||
|
|
||||||
if (isMonetizationSharing && modalData) {
|
|
||||||
return (
|
|
||||||
<TableAboutModal
|
|
||||||
isOpen={isOpen}
|
|
||||||
listItemData={modalData.listItemData}
|
|
||||||
headerIconName="channel"
|
|
||||||
header={modalData.header}
|
|
||||||
footer={modalData.footer}
|
|
||||||
buttonText={oldLang('RevenueSharingAdsUnderstood')}
|
|
||||||
onClose={onClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
className={styles.root}
|
|
||||||
contentClassName={styles.content}
|
|
||||||
onClose={onClose}
|
|
||||||
>
|
|
||||||
{regularAdContent}
|
|
||||||
<Button
|
|
||||||
size="smaller"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
{oldLang('RevenueSharingAdsUnderstood')}
|
|
||||||
</Button>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(AboutAdsModal);
|
|
||||||
@ -7,7 +7,7 @@ import styles from './BadgeButton.module.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: NoneToVoidFunction;
|
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BadgeButton = ({
|
const BadgeButton = ({
|
||||||
|
|||||||
@ -227,10 +227,11 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
}, [firstUnreadId]);
|
}, [firstUnreadId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (areAdsEnabled && isChannelChat && isSynced && isReady) {
|
const canHaveAds = isChannelChat || isBot;
|
||||||
|
if (areAdsEnabled && canHaveAds && isSynced && isReady) {
|
||||||
loadSponsoredMessages({ peerId: chatId });
|
loadSponsoredMessages({ peerId: chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, isSynced, isReady, isChannelChat, areAdsEnabled]);
|
}, [chatId, isSynced, isReady, isChannelChat, isBot, areAdsEnabled]);
|
||||||
|
|
||||||
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
|
// Updated only once when messages are loaded (as we want the unread divider to keep its position)
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
@ -695,7 +696,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
) : hasMessages ? (
|
) : hasMessages ? (
|
||||||
<MessageListContent
|
<MessageListContent
|
||||||
areAdsEnabled={areAdsEnabled}
|
canShowAds={areAdsEnabled && isChannelChat}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
isComments={isComments}
|
isComments={isComments}
|
||||||
isChannelChat={isChannelChat}
|
isChannelChat={isChannelChat}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import SponsoredMessage from './message/SponsoredMessage';
|
|||||||
import MessageListBotInfo from './MessageListBotInfo';
|
import MessageListBotInfo from './MessageListBotInfo';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
areAdsEnabled?: boolean;
|
canShowAds?: boolean;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: ThreadId;
|
threadId: ThreadId;
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
@ -68,7 +68,7 @@ interface OwnProps {
|
|||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
|
|
||||||
const MessageListContent: FC<OwnProps> = ({
|
const MessageListContent: FC<OwnProps> = ({
|
||||||
areAdsEnabled,
|
canShowAds,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
messageIds,
|
messageIds,
|
||||||
@ -308,7 +308,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
key="fab-trigger"
|
key="fab-trigger"
|
||||||
className="fab-trigger"
|
className="fab-trigger"
|
||||||
/>
|
/>
|
||||||
{areAdsEnabled && isViewportNewest && (
|
{canShowAds && isViewportNewest && (
|
||||||
<SponsoredMessage
|
<SponsoredMessage
|
||||||
key={chatId}
|
key={chatId}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { applyAnimationState, type PaneState } from './hooks/useHeaderPane';
|
|||||||
|
|
||||||
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
import GroupCallTopPane from '../calls/group/GroupCallTopPane';
|
||||||
import AudioPlayer from './panes/AudioPlayer';
|
import AudioPlayer from './panes/AudioPlayer';
|
||||||
|
import BotAdPane from './panes/BotAdPane';
|
||||||
import ChatReportPane from './panes/ChatReportPane';
|
import ChatReportPane from './panes/ChatReportPane';
|
||||||
import HeaderPinnedMessage from './panes/HeaderPinnedMessage';
|
import HeaderPinnedMessage from './panes/HeaderPinnedMessage';
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ const MiddleHeaderPanes = ({
|
|||||||
const [getPinnedState, setPinnedState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getPinnedState, setPinnedState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
const [getGroupCallState, setGroupCallState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getGroupCallState, setGroupCallState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
const [getChatReportState, setChatReportState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
const [getChatReportState, setChatReportState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
|
const [getBotAdState, setBotAdState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||||
|
|
||||||
const isPinnedMessagesFullWidth = isAudioPlayerRendered || !isDesktop;
|
const isPinnedMessagesFullWidth = isAudioPlayerRendered || !isDesktop;
|
||||||
|
|
||||||
@ -85,9 +87,10 @@ const MiddleHeaderPanes = ({
|
|||||||
const pinnedState = getPinnedState();
|
const pinnedState = getPinnedState();
|
||||||
const groupCallState = getGroupCallState();
|
const groupCallState = getGroupCallState();
|
||||||
const chatReportState = getChatReportState();
|
const chatReportState = getChatReportState();
|
||||||
|
const botAdState = getBotAdState();
|
||||||
|
|
||||||
// Keep in sync with the order of the panes in the DOM
|
// Keep in sync with the order of the panes in the DOM
|
||||||
const stateArray = [audioPlayerState, groupCallState, chatReportState, pinnedState];
|
const stateArray = [audioPlayerState, groupCallState, chatReportState, pinnedState, botAdState];
|
||||||
|
|
||||||
const isFirstRender = isFirstRenderRef.current;
|
const isFirstRender = isFirstRenderRef.current;
|
||||||
const totalHeight = stateArray.reduce((acc, state) => acc + state.height, 0);
|
const totalHeight = stateArray.reduce((acc, state) => acc + state.height, 0);
|
||||||
@ -100,7 +103,7 @@ const MiddleHeaderPanes = ({
|
|||||||
setExtraStyles(middleColumn, {
|
setExtraStyles(middleColumn, {
|
||||||
'--middle-header-panes-height': `${totalHeight}px`,
|
'--middle-header-panes-height': `${totalHeight}px`,
|
||||||
});
|
});
|
||||||
}, [getAudioPlayerState, getGroupCallState, getPinnedState, getChatReportState]);
|
}, [getAudioPlayerState, getGroupCallState, getPinnedState, getChatReportState, getBotAdState]);
|
||||||
|
|
||||||
if (!shouldRender) return undefined;
|
if (!shouldRender) return undefined;
|
||||||
|
|
||||||
@ -136,6 +139,11 @@ const MiddleHeaderPanes = ({
|
|||||||
isFullWidth
|
isFullWidth
|
||||||
shouldHide={!isPinnedMessagesFullWidth}
|
shouldHide={!isPinnedMessagesFullWidth}
|
||||||
/>
|
/>
|
||||||
|
<BotAdPane
|
||||||
|
chatId={chatId}
|
||||||
|
messageListType={messageListType}
|
||||||
|
onPaneStateChange={setBotAdState}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
type RefObject,
|
type RefObject,
|
||||||
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
@ -12,6 +13,8 @@ import { requestForcedReflow, requestNextMutation } from '../../../lib/fasterdom
|
|||||||
|
|
||||||
import useTimeout from '../../../hooks/schedulers/useTimeout';
|
import useTimeout from '../../../hooks/schedulers/useTimeout';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||||
|
import useThrottledCallback from '../../../hooks/useThrottledCallback';
|
||||||
|
|
||||||
export interface PaneState {
|
export interface PaneState {
|
||||||
element?: HTMLElement;
|
element?: HTMLElement;
|
||||||
@ -21,16 +24,19 @@ export interface PaneState {
|
|||||||
|
|
||||||
// Max slide transition duration
|
// Max slide transition duration
|
||||||
const CLOSE_DURATION = 450;
|
const CLOSE_DURATION = 450;
|
||||||
|
const RESIZE_THROTTLE = 100;
|
||||||
|
|
||||||
export default function useHeaderPane<RefType extends HTMLElement = HTMLDivElement>({
|
export default function useHeaderPane<RefType extends HTMLElement = HTMLDivElement>({
|
||||||
ref: providedRef,
|
ref: providedRef,
|
||||||
isOpen,
|
isOpen,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
withResizeObserver,
|
||||||
onStateChange,
|
onStateChange,
|
||||||
} : {
|
} : {
|
||||||
ref?: RefObject<RefType | null>;
|
ref?: RefObject<RefType | null>;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
withResizeObserver?: boolean;
|
||||||
onStateChange?: (state: PaneState) => void;
|
onStateChange?: (state: PaneState) => void;
|
||||||
}) {
|
}) {
|
||||||
const [shouldRender, setShouldRender] = useState(true);
|
const [shouldRender, setShouldRender] = useState(true);
|
||||||
@ -38,6 +44,8 @@ export default function useHeaderPane<RefType extends HTMLElement = HTMLDivEleme
|
|||||||
const localRef = useRef<RefType>(null);
|
const localRef = useRef<RefType>(null);
|
||||||
const ref = providedRef || localRef;
|
const ref = providedRef || localRef;
|
||||||
|
|
||||||
|
const lastHeightRef = useRef(0);
|
||||||
|
|
||||||
const reset = useLastCallback(() => {
|
const reset = useLastCallback(() => {
|
||||||
setShouldRender(true);
|
setShouldRender(true);
|
||||||
onStateChange?.({
|
onStateChange?.({
|
||||||
@ -65,7 +73,8 @@ export default function useHeaderPane<RefType extends HTMLElement = HTMLDivEleme
|
|||||||
setShouldRender(false);
|
setShouldRender(false);
|
||||||
}, !isOpen ? CLOSE_DURATION : undefined);
|
}, !isOpen ? CLOSE_DURATION : undefined);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
// Should be `useCallback` to trigger effect on deps change
|
||||||
|
const handleUpdate = useCallback(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
if (isDisabled || !element || !shouldRender) return;
|
if (isDisabled || !element || !shouldRender) return;
|
||||||
|
|
||||||
@ -80,6 +89,7 @@ export default function useHeaderPane<RefType extends HTMLElement = HTMLDivEleme
|
|||||||
|
|
||||||
requestForcedReflow(() => {
|
requestForcedReflow(() => {
|
||||||
const currentHeight = element.offsetHeight;
|
const currentHeight = element.offsetHeight;
|
||||||
|
lastHeightRef.current = currentHeight;
|
||||||
return () => {
|
return () => {
|
||||||
onStateChange?.({
|
onStateChange?.({
|
||||||
element,
|
element,
|
||||||
@ -90,6 +100,22 @@ export default function useHeaderPane<RefType extends HTMLElement = HTMLDivEleme
|
|||||||
});
|
});
|
||||||
}, [isOpen, shouldRender, isDisabled, ref, onStateChange]);
|
}, [isOpen, shouldRender, isDisabled, ref, onStateChange]);
|
||||||
|
|
||||||
|
const handleResize = useThrottledCallback(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const newHeight = element.offsetHeight;
|
||||||
|
if (newHeight === lastHeightRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdate();
|
||||||
|
}, [handleUpdate, ref], RESIZE_THROTTLE, true);
|
||||||
|
|
||||||
|
useLayoutEffect(handleUpdate, [handleUpdate]);
|
||||||
|
|
||||||
|
useResizeObserver(ref, handleResize, !withResizeObserver || !shouldRender);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldRender,
|
shouldRender,
|
||||||
ref,
|
ref,
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import type {
|
|||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPoll,
|
ApiPoll,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiSponsoredMessage,
|
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
@ -51,7 +50,7 @@ type OwnProps = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
anchor: IAnchorPosition;
|
anchor: IAnchorPosition;
|
||||||
targetHref?: string;
|
targetHref?: string;
|
||||||
message: ApiMessage | ApiSponsoredMessage;
|
message: ApiMessage;
|
||||||
poll?: ApiPoll;
|
poll?: ApiPoll;
|
||||||
story?: ApiTypeStory;
|
story?: ApiTypeStory;
|
||||||
canSendNow?: boolean;
|
canSendNow?: boolean;
|
||||||
@ -119,10 +118,6 @@ type OwnProps = {
|
|||||||
onClosePoll?: NoneToVoidFunction;
|
onClosePoll?: NoneToVoidFunction;
|
||||||
onShowSeenBy?: NoneToVoidFunction;
|
onShowSeenBy?: NoneToVoidFunction;
|
||||||
onShowReactors?: NoneToVoidFunction;
|
onShowReactors?: NoneToVoidFunction;
|
||||||
onAboutAdsClick?: NoneToVoidFunction;
|
|
||||||
onSponsoredHide?: NoneToVoidFunction;
|
|
||||||
onSponsorInfo?: NoneToVoidFunction;
|
|
||||||
onSponsoredReport?: NoneToVoidFunction;
|
|
||||||
onTranslate?: NoneToVoidFunction;
|
onTranslate?: NoneToVoidFunction;
|
||||||
onShowOriginal?: NoneToVoidFunction;
|
onShowOriginal?: NoneToVoidFunction;
|
||||||
onSelectLanguage?: NoneToVoidFunction;
|
onSelectLanguage?: NoneToVoidFunction;
|
||||||
@ -215,10 +210,6 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
onSendPaidReaction,
|
onSendPaidReaction,
|
||||||
onShowPaidReactionModal,
|
onShowPaidReactionModal,
|
||||||
onCopyMessages,
|
onCopyMessages,
|
||||||
onAboutAdsClick,
|
|
||||||
onSponsoredHide,
|
|
||||||
onSponsorInfo,
|
|
||||||
onSponsoredReport,
|
|
||||||
onReactionPickerOpen,
|
onReactionPickerOpen,
|
||||||
onTranslate,
|
onTranslate,
|
||||||
onShowOriginal,
|
onShowOriginal,
|
||||||
@ -234,10 +225,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const noReactions = !isPrivate && !enabledReactions;
|
const noReactions = !isPrivate && !enabledReactions;
|
||||||
const withReactions = canShowReactionList && !noReactions;
|
const withReactions = canShowReactionList && !noReactions;
|
||||||
const isSponsoredMessage = !('id' in message);
|
|
||||||
const isEdited = ('isEdited' in message) && message.isEdited;
|
const isEdited = ('isEdited' in message) && message.isEdited;
|
||||||
const messageId = !isSponsoredMessage ? message.id : '';
|
const seenByDates = message.seenByDates;
|
||||||
const seenByDates = !isSponsoredMessage ? message.seenByDates : undefined;
|
|
||||||
|
|
||||||
const [areItemsHidden, hideItems] = useFlag();
|
const [areItemsHidden, hideItems] = useFlag();
|
||||||
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
||||||
@ -286,23 +275,19 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
const copyOptions = isSponsoredMessage
|
const copyOptions = getMessageCopyOptions(
|
||||||
? []
|
message,
|
||||||
: getMessageCopyOptions(
|
groupStatetefulContent({ poll, story }),
|
||||||
message,
|
targetHref,
|
||||||
groupStatetefulContent({ poll, story }),
|
canCopy,
|
||||||
targetHref,
|
handleAfterCopy,
|
||||||
canCopy,
|
canCopyLink ? onCopyLink : undefined,
|
||||||
handleAfterCopy,
|
onCopyMessages,
|
||||||
canCopyLink ? onCopyLink : undefined,
|
onCopyNumber,
|
||||||
onCopyMessages,
|
);
|
||||||
onCopyNumber,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getTriggerElement = useLastCallback(() => {
|
const getTriggerElement = useLastCallback(() => {
|
||||||
return isSponsoredMessage
|
return document.querySelector(`.Transition_slide-active > .MessageList div[data-message-id="${message.id}"]`);
|
||||||
? document.querySelector('.Transition_slide-active > .MessageList .SponsoredMessage')
|
|
||||||
: document.querySelector(`.Transition_slide-active > .MessageList div[data-message-id="${messageId}"]`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRootElement = useLastCallback(() => document.querySelector('.Transition_slide-active > .MessageList'));
|
const getRootElement = useLastCallback(() => document.querySelector('.Transition_slide-active > .MessageList'));
|
||||||
@ -368,7 +353,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
topReactions={topReactions}
|
topReactions={topReactions}
|
||||||
allAvailableReactions={availableReactions}
|
allAvailableReactions={availableReactions}
|
||||||
defaultTagReactions={defaultTagReactions}
|
defaultTagReactions={defaultTagReactions}
|
||||||
currentReactions={!isSponsoredMessage ? message.reactions?.results : undefined}
|
currentReactions={message.reactions?.results}
|
||||||
reactionsLimit={reactionsLimit}
|
reactionsLimit={reactionsLimit}
|
||||||
onToggleReaction={onToggleReaction!}
|
onToggleReaction={onToggleReaction!}
|
||||||
onSendPaidReaction={onSendPaidReaction}
|
onSendPaidReaction={onSendPaidReaction}
|
||||||
@ -464,26 +449,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isSponsoredMessage && message.sponsorInfo && (
|
{(canShowSeenBy || canShowReactionsCount) && (
|
||||||
<MenuItem icon="channel" onClick={onSponsorInfo}>{lang('SponsoredMessageSponsor')}</MenuItem>
|
|
||||||
)}
|
|
||||||
{isSponsoredMessage && (
|
|
||||||
<MenuItem icon="info" onClick={onAboutAdsClick}>
|
|
||||||
{lang(message.canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{isSponsoredMessage && message.canReport && (
|
|
||||||
<MenuItem icon="hand-stop" onClick={onSponsoredReport}>
|
|
||||||
{lang('ReportAd')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{isSponsoredMessage && onSponsoredHide && (
|
|
||||||
<>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem icon="close-circle" onClick={onSponsoredHide}>{lang('HideAd')}</MenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{(canShowSeenBy || canShowReactionsCount) && !isSponsoredMessage && (
|
|
||||||
<>
|
<>
|
||||||
<MenuSeparator size={hasCustomEmoji ? 'thin' : 'thick'} />
|
<MenuSeparator size={hasCustomEmoji ? 'thin' : 'thick'} />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@ -518,10 +484,10 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{((!isSponsoredMessage && (canLoadReadDate || shouldRenderShowWhen)) || isEdited) && (
|
{(canLoadReadDate || shouldRenderShowWhen || isEdited) && (
|
||||||
<MenuSeparator size={hasCustomEmoji ? 'thin' : 'thick'} />
|
<MenuSeparator size={hasCustomEmoji ? 'thin' : 'thick'} />
|
||||||
)}
|
)}
|
||||||
{!isSponsoredMessage && (canLoadReadDate || shouldRenderShowWhen) && (
|
{(canLoadReadDate || shouldRenderShowWhen) && (
|
||||||
<ReadTimeMenuItem
|
<ReadTimeMenuItem
|
||||||
canLoadReadDate={canLoadReadDate}
|
canLoadReadDate={canLoadReadDate}
|
||||||
shouldRenderShowWhen={shouldRenderShowWhen}
|
shouldRenderShowWhen={shouldRenderShowWhen}
|
||||||
|
|||||||
@ -30,6 +30,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-type {
|
.message-type {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
padding-inline-end: 0.25rem;
|
padding-inline-end: 0.25rem;
|
||||||
|
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
@ -121,17 +124,6 @@
|
|||||||
|
|
||||||
.ad-about {
|
.ad-about {
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
margin-inline-start: 0.25rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
padding-inline: 0.375rem;
|
|
||||||
transition: 150ms filter ease-in;
|
|
||||||
background: var(--accent-background-active-color);
|
|
||||||
cursor: var(--custom-cursor, pointer);
|
|
||||||
filter: brightness(1);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-media {
|
.has-media {
|
||||||
|
|||||||
@ -27,13 +27,12 @@ import { calculateMediaDimensions, getMinMediaWidth, MIN_MEDIA_WIDTH_WITH_TEXT }
|
|||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
import { type ObserveFn, useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import AboutAdsModal from '../../common/AboutAdsModal.async';
|
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
|
import BadgeButton from '../../common/BadgeButton';
|
||||||
import Icon from '../../common/icons/Icon';
|
import Icon from '../../common/icons/Icon';
|
||||||
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
import PeerColorWrapper from '../../common/PeerColorWrapper';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -77,8 +76,8 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
openUrl,
|
openUrl,
|
||||||
hideSponsoredMessages,
|
hideSponsoredMessages,
|
||||||
clickSponsoredMessage,
|
clickSponsoredMessage,
|
||||||
reportSponsoredMessage,
|
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
|
openAboutAdsModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -101,7 +100,6 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref, undefined, true, IS_ANDROID);
|
} = useContextMenuHandlers(ref, undefined, true, IS_ANDROID);
|
||||||
const [isAboutAdsModalOpen, openAboutAdsModal, closeAboutAdsModal] = useFlag(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return shouldObserve ? observeIntersection(contentRef.current!, (target) => {
|
return shouldObserve ? observeIntersection(contentRef.current!, (target) => {
|
||||||
@ -116,10 +114,6 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
handleBeforeContextMenu(e);
|
handleBeforeContextMenu(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReportSponsoredMessage = useLastCallback(() => {
|
|
||||||
reportSponsoredMessage({ peerId: chatId, randomId: message!.randomId });
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleHideSponsoredMessage = useLastCallback(() => {
|
const handleHideSponsoredMessage = useLastCallback(() => {
|
||||||
hideSponsoredMessages();
|
hideSponsoredMessages();
|
||||||
});
|
});
|
||||||
@ -135,7 +129,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
clickSponsoredMessage({ isMedia: photo || isGif ? true : undefined, peerId: chatId });
|
clickSponsoredMessage({ isMedia: photo || isGif ? true : undefined, peerId: chatId });
|
||||||
openUrl({ url: message!.url, shouldSkipModal: true });
|
openUrl({ url: message.url, shouldSkipModal: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenMedia = useLastCallback(() => {
|
const handleOpenMedia = useLastCallback(() => {
|
||||||
@ -147,6 +141,10 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOpenAboutAdsModal = useLastCallback(() => {
|
||||||
|
openAboutAdsModal({ chatId });
|
||||||
|
});
|
||||||
|
|
||||||
const extraPadding = 0;
|
const extraPadding = 0;
|
||||||
|
|
||||||
const sizeCalculations = useMemo(() => {
|
const sizeCalculations = useMemo(() => {
|
||||||
@ -283,7 +281,9 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
<span className={buildClassName('message-title message-type', hasMedia && 'has-media')}>
|
<span className={buildClassName('message-title message-type', hasMedia && 'has-media')}>
|
||||||
{message!.isRecommended ? lang('Message.RecommendedLabel') : lang('SponsoredMessage')}
|
{message!.isRecommended ? lang('Message.RecommendedLabel') : lang('SponsoredMessage')}
|
||||||
<span onClick={openAboutAdsModal} className="ad-about">{lang('SponsoredMessageAdWhatIsThis')}</span>
|
<BadgeButton onClick={handleOpenAboutAdsModal} className="ad-about">
|
||||||
|
{lang('SponsoredMessageAdWhatIsThis')}
|
||||||
|
</BadgeButton>
|
||||||
</span>
|
</span>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</PeerColorWrapper>
|
</PeerColorWrapper>
|
||||||
@ -318,18 +318,12 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
<SponsoredMessageContextMenuContainer
|
<SponsoredMessageContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
anchor={contextMenuAnchor}
|
anchor={contextMenuAnchor}
|
||||||
|
triggerRef={ref}
|
||||||
message={message!}
|
message={message!}
|
||||||
onAboutAdsClick={openAboutAdsModal}
|
|
||||||
onReportAd={handleReportSponsoredMessage}
|
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<AboutAdsModal
|
|
||||||
isOpen={isAboutAdsModalOpen}
|
|
||||||
isMonetizationSharing={message.canReport}
|
|
||||||
onClose={closeAboutAdsModal}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo, useRef,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiSponsoredMessage,
|
||||||
|
} from '../../../api/types';
|
||||||
|
import type { IAnchorPosition } from '../../../types';
|
||||||
|
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Menu from '../../ui/Menu';
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import MenuSeparator from '../../ui/MenuSeparator';
|
||||||
|
|
||||||
|
import './MessageContextMenu.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
anchor: IAnchorPosition;
|
||||||
|
message: ApiSponsoredMessage;
|
||||||
|
triggerRef: React.RefObject<HTMLElement>;
|
||||||
|
shouldSkipAbout?: boolean;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
|
onAboutAdsClick: NoneToVoidFunction;
|
||||||
|
onSponsoredHide: NoneToVoidFunction;
|
||||||
|
onSponsorInfo?: NoneToVoidFunction;
|
||||||
|
onSponsoredReport?: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SponsoredMessageContextMenu: FC<OwnProps> = ({
|
||||||
|
isOpen,
|
||||||
|
message,
|
||||||
|
anchor,
|
||||||
|
triggerRef,
|
||||||
|
shouldSkipAbout,
|
||||||
|
onClose,
|
||||||
|
onCloseAnimationEnd,
|
||||||
|
onAboutAdsClick,
|
||||||
|
onSponsoredHide,
|
||||||
|
onSponsorInfo,
|
||||||
|
onSponsoredReport,
|
||||||
|
}) => {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const lang = useOldLang();
|
||||||
|
|
||||||
|
const getTriggerElement = useLastCallback(() => triggerRef.current);
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
const getMenuElement = useLastCallback(() => menuRef.current);
|
||||||
|
const getRootElement = useLastCallback(() => document.body);
|
||||||
|
|
||||||
|
const isSeparatorNeeded = message.sponsorInfo || !shouldSkipAbout || message.canReport;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
ref={menuRef}
|
||||||
|
isOpen={isOpen}
|
||||||
|
anchor={anchor}
|
||||||
|
withPortal
|
||||||
|
className="with-menu-transitions"
|
||||||
|
getLayout={getLayout}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
onClose={onClose}
|
||||||
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
|
>
|
||||||
|
{message.sponsorInfo && onSponsorInfo && (
|
||||||
|
<MenuItem icon="channel" onClick={onSponsorInfo}>{lang('SponsoredMessageSponsor')}</MenuItem>
|
||||||
|
)}
|
||||||
|
{!shouldSkipAbout && (
|
||||||
|
<MenuItem icon="info" onClick={onAboutAdsClick}>
|
||||||
|
{lang(message.canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{message.canReport && onSponsoredReport && (
|
||||||
|
<MenuItem icon="hand-stop" onClick={onSponsoredReport}>
|
||||||
|
{lang('ReportAd')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{isSeparatorNeeded && <MenuSeparator />}
|
||||||
|
<MenuItem icon="close-circle" onClick={onSponsoredHide}>
|
||||||
|
{lang('HideAd')}
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SponsoredMessageContextMenu);
|
||||||
@ -5,60 +5,71 @@ import { getActions } from '../../../global';
|
|||||||
import type { ApiSponsoredMessage } from '../../../api/types';
|
import type { ApiSponsoredMessage } from '../../../api/types';
|
||||||
import type { IAnchorPosition } from '../../../types';
|
import type { IAnchorPosition } from '../../../types';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import MessageContextMenu from './MessageContextMenu';
|
import SponsoredMessageContextMenu from './SponsoredMessageContextMenu';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
message: ApiSponsoredMessage;
|
message: ApiSponsoredMessage;
|
||||||
anchor: IAnchorPosition;
|
anchor: IAnchorPosition;
|
||||||
onAboutAdsClick: NoneToVoidFunction;
|
triggerRef: React.RefObject<HTMLElement>;
|
||||||
onReportAd: NoneToVoidFunction;
|
shouldSkipAbout?: boolean;
|
||||||
|
onItemClick?: NoneToVoidFunction;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd: NoneToVoidFunction;
|
onCloseAnimationEnd: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
||||||
|
isOpen,
|
||||||
message,
|
message,
|
||||||
anchor,
|
anchor,
|
||||||
onAboutAdsClick,
|
triggerRef,
|
||||||
onReportAd,
|
shouldSkipAbout,
|
||||||
|
onItemClick,
|
||||||
onClose,
|
onClose,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
}) => {
|
}) => {
|
||||||
const { openPremiumModal, showDialog } = getActions();
|
const {
|
||||||
|
openAboutAdsModal,
|
||||||
|
showDialog,
|
||||||
|
reportSponsoredMessage,
|
||||||
|
hideSponsoredMessages,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
const [isMenuOpen, , closeMenu] = useFlag(true);
|
const { ref } = useShowTransition({
|
||||||
const { transitionClassNames } = useShowTransitionDeprecated(isMenuOpen, onCloseAnimationEnd, undefined, false);
|
isOpen,
|
||||||
|
onCloseAnimationEnd,
|
||||||
const handleAboutAdsOpen = useLastCallback(() => {
|
|
||||||
onAboutAdsClick();
|
|
||||||
closeMenu();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSponsoredHide = useLastCallback(() => {
|
const handleItemClick = useLastCallback(() => {
|
||||||
closeMenu();
|
onItemClick?.();
|
||||||
openPremiumModal();
|
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleAboutAdsOpen = useLastCallback(() => {
|
||||||
|
openAboutAdsModal({ chatId: message.chatId });
|
||||||
|
handleItemClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSponsoredHide = useLastCallback(() => {
|
||||||
|
hideSponsoredMessages();
|
||||||
|
handleItemClick();
|
||||||
|
});
|
||||||
|
|
||||||
const handleSponsorInfo = useLastCallback(() => {
|
const handleSponsorInfo = useLastCallback(() => {
|
||||||
closeMenu();
|
|
||||||
showDialog({
|
showDialog({
|
||||||
data: {
|
data: {
|
||||||
message: [message.sponsorInfo, message.additionalInfo].join('\n'),
|
message: [message.sponsorInfo, message.additionalInfo].join('\n'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
handleItemClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleReportSponsoredMessage = useLastCallback(() => {
|
const handleReportSponsoredMessage = useLastCallback(() => {
|
||||||
closeMenu();
|
reportSponsoredMessage({ peerId: message.chatId, randomId: message.randomId });
|
||||||
onReportAd();
|
handleItemClick();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
@ -66,13 +77,15 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('ContextMenuContainer', transitionClassNames)}>
|
<div ref={ref} className="ContextMenuContainer">
|
||||||
<MessageContextMenu
|
<SponsoredMessageContextMenu
|
||||||
isOpen={isMenuOpen}
|
isOpen={isOpen}
|
||||||
anchor={anchor}
|
anchor={anchor}
|
||||||
|
triggerRef={triggerRef}
|
||||||
message={message}
|
message={message}
|
||||||
onClose={closeMenu}
|
shouldSkipAbout={shouldSkipAbout}
|
||||||
onCloseAnimationEnd={closeMenu}
|
onClose={onClose}
|
||||||
|
onCloseAnimationEnd={onClose}
|
||||||
onAboutAdsClick={handleAboutAdsOpen}
|
onAboutAdsClick={handleAboutAdsOpen}
|
||||||
onSponsoredHide={handleSponsoredHide}
|
onSponsoredHide={handleSponsoredHide}
|
||||||
onSponsorInfo={handleSponsorInfo}
|
onSponsorInfo={handleSponsorInfo}
|
||||||
|
|||||||
53
src/components/middle/panes/BotAdPane.module.scss
Normal file
53
src/components/middle/panes/BotAdPane.module.scss
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
@include mixins.header-pane;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
line-height: 1.25;
|
||||||
|
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
// Slight variation from mixins.header-pane
|
||||||
|
padding-left: max(1rem, env(safe-area-inset-left));
|
||||||
|
transition: background-color 0.2s, transform var(--slide-transition);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
--radius: 0.25rem;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title, .info {
|
||||||
|
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aboutAd {
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
}
|
||||||
153
src/components/middle/panes/BotAdPane.tsx
Normal file
153
src/components/middle/panes/BotAdPane.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import React, { memo, useEffect } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiSponsoredMessage } from '../../../api/types';
|
||||||
|
import type { MessageListType } from '../../../global/types';
|
||||||
|
|
||||||
|
import { selectBot, selectSponsoredMessage } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { getApiPeerColorClass } from '../../common/helpers/peerColor';
|
||||||
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useHeaderPane, { type PaneState } from '../hooks/useHeaderPane';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import BadgeButton from '../../common/BadgeButton';
|
||||||
|
import SponsoredMessageContextMenuContainer from '../message/SponsoredMessageContextMenuContainer';
|
||||||
|
|
||||||
|
import styles from './BotAdPane.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
chatId: string;
|
||||||
|
messageListType: MessageListType;
|
||||||
|
onPaneStateChange?: (state: PaneState) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isBot?: boolean;
|
||||||
|
sponsoredMessage?: ApiSponsoredMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BotAdPane = ({
|
||||||
|
chatId,
|
||||||
|
isBot,
|
||||||
|
messageListType,
|
||||||
|
sponsoredMessage,
|
||||||
|
onPaneStateChange,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const {
|
||||||
|
viewSponsoredMessage,
|
||||||
|
openUrl,
|
||||||
|
clickSponsoredMessage,
|
||||||
|
openAboutAdsModal,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isOpen = Boolean(isBot && sponsoredMessage && messageListType === 'thread');
|
||||||
|
|
||||||
|
const renderingSponsoredMessage = useCurrentOrPrev(sponsoredMessage);
|
||||||
|
|
||||||
|
const { ref, shouldRender } = useHeaderPane({
|
||||||
|
isOpen,
|
||||||
|
withResizeObserver: true,
|
||||||
|
onStateChange: onPaneStateChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(ref, !shouldRender, true);
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
if (!renderingSponsoredMessage) return;
|
||||||
|
|
||||||
|
clickSponsoredMessage({ peerId: chatId });
|
||||||
|
openUrl({ url: renderingSponsoredMessage.url, shouldSkipModal: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAboutClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openAboutAdsModal({ chatId });
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldRender && sponsoredMessage) {
|
||||||
|
viewSponsoredMessage({ peerId: chatId });
|
||||||
|
}
|
||||||
|
}, [shouldRender, sponsoredMessage, chatId]);
|
||||||
|
|
||||||
|
if (!shouldRender || !renderingSponsoredMessage) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
peerColor,
|
||||||
|
content,
|
||||||
|
photo,
|
||||||
|
title,
|
||||||
|
} = renderingSponsoredMessage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={styles.root}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseDown={handleBeforeContextMenu}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
>
|
||||||
|
<div className={buildClassName(styles.content, peerColor && getApiPeerColorClass(peerColor))}>
|
||||||
|
<span className={styles.info}>
|
||||||
|
{lang('SponsoredMessageAd')}
|
||||||
|
<BadgeButton onClick={handleAboutClick} className={styles.aboutAd}>
|
||||||
|
{lang('SponsoredMessageAdWhatIsThis')}
|
||||||
|
</BadgeButton>
|
||||||
|
</span>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
{content.text && (
|
||||||
|
<div className={styles.text}>
|
||||||
|
{renderTextWithEntities({
|
||||||
|
text: content.text.text,
|
||||||
|
entities: content.text.entities,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{photo && (
|
||||||
|
<Avatar
|
||||||
|
size="large"
|
||||||
|
photo={photo}
|
||||||
|
className={styles.avatar}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{contextMenuAnchor && (
|
||||||
|
<SponsoredMessageContextMenuContainer
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
triggerRef={ref}
|
||||||
|
message={renderingSponsoredMessage}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { chatId }): StateProps => {
|
||||||
|
const bot = selectBot(global, chatId);
|
||||||
|
const sponsoredMessage = selectSponsoredMessage(global, chatId);
|
||||||
|
return {
|
||||||
|
isBot: Boolean(bot),
|
||||||
|
sponsoredMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(BotAdPane));
|
||||||
@ -7,6 +7,7 @@ import { selectTabState } from '../../global/selectors';
|
|||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
|
|
||||||
import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal.async';
|
import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal.async';
|
||||||
|
import AboutAdsModal from './aboutAds/AboutAdsModal.async';
|
||||||
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
||||||
import BoostModal from './boost/BoostModal.async';
|
import BoostModal from './boost/BoostModal.async';
|
||||||
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||||
@ -55,7 +56,8 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'giftModal' |
|
'giftModal' |
|
||||||
'isGiftRecipientPickerOpen' |
|
'isGiftRecipientPickerOpen' |
|
||||||
'isWebAppsCloseConfirmationModalOpen' |
|
'isWebAppsCloseConfirmationModalOpen' |
|
||||||
'giftInfoModal'
|
'giftInfoModal' |
|
||||||
|
'aboutAdsModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -94,6 +96,7 @@ const MODALS: ModalRegistry = {
|
|||||||
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
||||||
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
||||||
giftInfoModal: GiftInfoModal,
|
giftInfoModal: GiftInfoModal,
|
||||||
|
aboutAdsModal: AboutAdsModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React from '../../lib/teact/teact';
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { OwnProps } from './AboutAdsModal';
|
import type { OwnProps } from './AboutAdsModal';
|
||||||
|
|
||||||
import { Bundles } from '../../util/moduleLoader';
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
const AboutAdsModalAsync: FC<OwnProps> = (props) => {
|
const AboutAdsModalAsync: FC<OwnProps> = (props) => {
|
||||||
const { isOpen } = props;
|
const { modal } = props;
|
||||||
const AboutAdsModal = useModuleLoader(Bundles.Extra, 'AboutAdsModal', !isOpen);
|
const AboutAdsModal = useModuleLoader(Bundles.Extra, 'AboutAdsModal', !modal);
|
||||||
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
return AboutAdsModal ? <AboutAdsModal {...props} /> : undefined;
|
return AboutAdsModal ? <AboutAdsModal {...props} /> : undefined;
|
||||||
@ -8,6 +8,12 @@
|
|||||||
padding-inline: 1.5rem;
|
padding-inline: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.moreButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.secondary {
|
.secondary {
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
@ -16,4 +22,5 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
185
src/components/modals/aboutAds/AboutAdsModal.tsx
Normal file
185
src/components/modals/aboutAds/AboutAdsModal.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiSponsoredMessage } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
import type { TableAboutData } from '../common/TableAboutModal';
|
||||||
|
|
||||||
|
import { selectSponsoredMessage } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
|
import SafeLink from '../../common/SafeLink';
|
||||||
|
import SponsoredMessageContextMenuContainer from '../../middle/message/SponsoredMessageContextMenuContainer';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
import TableAboutModal from '../common/TableAboutModal';
|
||||||
|
|
||||||
|
import styles from './AboutAdsModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
|
modal: TabState['aboutAdsModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
message?: ApiSponsoredMessage;
|
||||||
|
minLevelToRestrictAds?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AboutAdsModal = ({ message, minLevelToRestrictAds }: OwnProps & StateProps) => {
|
||||||
|
const { closeAboutAdsModal } = getActions();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const moreMenuRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const isOpen = Boolean(message);
|
||||||
|
const isMonetizationSharing = message?.canReport;
|
||||||
|
|
||||||
|
const renderingIsNewDesign = useCurrentOrPrev(isMonetizationSharing);
|
||||||
|
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
|
||||||
|
const regularAdContent = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>{oldLang('SponsoredMessageInfoScreen.Title')}</h3>
|
||||||
|
<p>{renderText(oldLang('SponsoredMessageInfoDescription1'), ['br'])}</p>
|
||||||
|
<p>{renderText(oldLang('SponsoredMessageInfoDescription2'), ['br'])}</p>
|
||||||
|
<p>{renderText(oldLang('SponsoredMessageInfoDescription3'), ['br'])}</p>
|
||||||
|
<p>
|
||||||
|
<SafeLink
|
||||||
|
url={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
||||||
|
text={oldLang('SponsoredMessageAlertLearnMoreUrl')}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>{renderText(oldLang('SponsoredMessageInfoDescription4'), ['br'])}</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [oldLang]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
|
handleContextMenu, handleContextMenuClose, handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(moreMenuRef, !renderingIsNewDesign);
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
closeAboutAdsModal();
|
||||||
|
handleContextMenuClose();
|
||||||
|
handleContextMenuHide();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalData = useMemo(() => {
|
||||||
|
if (!isOpen) return undefined;
|
||||||
|
|
||||||
|
const header = (
|
||||||
|
<>
|
||||||
|
<h3 className={styles.title}>{oldLang('AboutRevenueSharingAds')}</h3>
|
||||||
|
<p className={buildClassName(styles.description, styles.secondary)}>
|
||||||
|
{oldLang('RevenueSharingAdsAlertSubtitle')}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
ref={moreMenuRef}
|
||||||
|
round
|
||||||
|
size="smaller"
|
||||||
|
color="translucent"
|
||||||
|
className={styles.moreButton}
|
||||||
|
onClick={handleContextMenu}
|
||||||
|
>
|
||||||
|
<Icon name="more" />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const listItemData = [
|
||||||
|
['lock', oldLang('RevenueSharingAdsInfo1Title'),
|
||||||
|
renderText(oldLang('RevenueSharingAdsInfo1Subtitle'), ['simple_markdown'])],
|
||||||
|
['revenue-split', oldLang('RevenueSharingAdsInfo2Title'),
|
||||||
|
renderText(oldLang('RevenueSharingAdsInfo2Subtitle'), ['simple_markdown'])],
|
||||||
|
['nochannel', oldLang('RevenueSharingAdsInfo3Title'),
|
||||||
|
renderText(oldLang('RevenueSharingAdsInfo3Subtitle', minLevelToRestrictAds), ['simple_markdown'])],
|
||||||
|
] satisfies TableAboutData;
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<h3 className={styles.title}>{renderText(oldLang('RevenueSharingAdsInfo4Title'), ['simple_markdown'])}</h3>
|
||||||
|
<p className={styles.description}>
|
||||||
|
{renderText(oldLang('RevenueSharingAdsInfo4Subtitle2', ''), ['simple_markdown'])}
|
||||||
|
<SafeLink
|
||||||
|
url={oldLang('PromoteUrl')}
|
||||||
|
text={oldLang('LearnMoreArrow')}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
header,
|
||||||
|
listItemData,
|
||||||
|
footer,
|
||||||
|
};
|
||||||
|
}, [isOpen, oldLang, handleContextMenu, minLevelToRestrictAds]);
|
||||||
|
|
||||||
|
if (renderingIsNewDesign) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableAboutModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
listItemData={modalData?.listItemData}
|
||||||
|
headerIconName="channel"
|
||||||
|
header={modalData?.header}
|
||||||
|
footer={modalData?.footer}
|
||||||
|
buttonText={oldLang('RevenueSharingAdsUnderstood')}
|
||||||
|
onClose={handleClose}
|
||||||
|
/>
|
||||||
|
{contextMenuAnchor && message && (
|
||||||
|
<SponsoredMessageContextMenuContainer
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
triggerRef={moreMenuRef}
|
||||||
|
message={message}
|
||||||
|
shouldSkipAbout
|
||||||
|
onItemClick={handleClose}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
className={styles.root}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{regularAdContent}
|
||||||
|
<Button
|
||||||
|
size="smaller"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
{oldLang('RevenueSharingAdsUnderstood')}
|
||||||
|
</Button>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { modal }): StateProps => {
|
||||||
|
const message = modal?.chatId ? selectSponsoredMessage(global, modal.chatId) : undefined;
|
||||||
|
const minLevelToRestrictAds = global.appConfig?.channelRestrictAdsLevelMin;
|
||||||
|
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
minLevelToRestrictAds,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(AboutAdsModal));
|
||||||
@ -31,9 +31,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
margin-block: 1rem;
|
margin-block: 1rem;
|
||||||
width: 110%;
|
width: calc(100% + 2rem); // Hack to cover modal paddings
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ const TableAboutModal = ({
|
|||||||
<Separator className={styles.separator} />
|
<Separator className={styles.separator} />
|
||||||
{footer}
|
{footer}
|
||||||
{buttonText && (
|
{buttonText && (
|
||||||
<Button onClick={onButtonClick || onClose}>{buttonText}</Button>
|
<Button size="smaller" onClick={onButtonClick || onClose}>{buttonText}</Button>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,12 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
:global(.modal-dialog) {
|
||||||
|
max-width: 23rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.modalTitle {
|
.modalTitle {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -15,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
margin-bottom: 0.25rem !important;
|
margin-bottom: 0rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionButton {
|
.optionButton {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../../
|
|||||||
|
|
||||||
import styles from './ReportAdModal.module.scss';
|
import styles from './ReportAdModal.module.scss';
|
||||||
|
|
||||||
const ADDED_PADDING = 40;
|
const ADDED_PADDING = 56;
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
modal: TabState['reportAdModal'];
|
modal: TabState['reportAdModal'];
|
||||||
@ -64,7 +64,11 @@ const ReportAdModal = ({
|
|||||||
const parts = template.split('{link}');
|
const parts = template.split('{link}');
|
||||||
return [
|
return [
|
||||||
parts[0],
|
parts[0],
|
||||||
<SafeLink text={lang('lng_report_sponsored_reported_link')} url={lang('ReportAd.Help_URL')} />,
|
<SafeLink
|
||||||
|
withNormalWordBreak
|
||||||
|
text={lang('lng_report_sponsored_reported_link')}
|
||||||
|
url={lang('ReportAd.Help_URL')}
|
||||||
|
/>,
|
||||||
parts[1],
|
parts[1],
|
||||||
];
|
];
|
||||||
}, [lang, modal]);
|
}, [lang, modal]);
|
||||||
@ -123,6 +127,7 @@ const ReportAdModal = ({
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
hasCloseButton
|
hasCloseButton
|
||||||
|
className={styles.root}
|
||||||
header={header}
|
header={header}
|
||||||
onClose={closeReportAdModal}
|
onClose={closeReportAdModal}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -453,7 +453,7 @@
|
|||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.5rem;
|
line-height: 1.25rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
& + .subtitle {
|
& + .subtitle {
|
||||||
|
|||||||
@ -62,6 +62,7 @@ import {
|
|||||||
isServiceNotificationMessage,
|
isServiceNotificationMessage,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
|
import { isApiPeerUser } from '../../helpers/peers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getActions, getGlobal, setGlobal,
|
addActionHandler, getActions, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
@ -112,6 +113,7 @@ import {
|
|||||||
selectFocusedMessageId,
|
selectFocusedMessageId,
|
||||||
selectForwardsCanBeSentToChat,
|
selectForwardsCanBeSentToChat,
|
||||||
selectForwardsContainVoiceMessages,
|
selectForwardsContainVoiceMessages,
|
||||||
|
selectIsChatBotNotStarted,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectLanguageCode,
|
selectLanguageCode,
|
||||||
@ -1631,6 +1633,10 @@ addActionHandler('loadSponsoredMessages', async (global, actions, payload): Prom
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isApiPeerUser(peer) && selectIsChatBotNotStarted(global, peer.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await callApi('fetchSponsoredMessages', { peer });
|
const result = await callApi('fetchSponsoredMessages', { peer });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1050,3 +1050,21 @@ addActionHandler('closeDeleteMessageModal', (global, actions, payload): ActionRe
|
|||||||
deleteMessageModal: undefined,
|
deleteMessageModal: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openAboutAdsModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
aboutAdsModal: {
|
||||||
|
chatId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeAboutAdsModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
aboutAdsModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -1251,8 +1251,7 @@ export function selectCanForwardMessages<T extends GlobalState>(global: T, chatI
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectSponsoredMessage<T extends GlobalState>(global: T, chatId: string) {
|
export function selectSponsoredMessage<T extends GlobalState>(global: T, chatId: string) {
|
||||||
const chat = selectChat(global, chatId);
|
const message = global.messages.sponsoredByChatId[chatId];
|
||||||
const message = chat && isChatChannel(chat) ? global.messages.sponsoredByChatId[chatId] : undefined;
|
|
||||||
|
|
||||||
return message && message.expiresAt >= Math.round(Date.now() / 1000) ? message : undefined;
|
return message && message.expiresAt >= Math.round(Date.now() / 1000) ? message : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -388,6 +388,10 @@ export type TabState = {
|
|||||||
messageId: number;
|
messageId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aboutAdsModal?: {
|
||||||
|
chatId: string;
|
||||||
|
};
|
||||||
|
|
||||||
reactionPicker?: {
|
reactionPicker?: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
messageId?: number;
|
messageId?: number;
|
||||||
@ -1750,6 +1754,10 @@ export interface ActionPayloads {
|
|||||||
randomId: string;
|
randomId: string;
|
||||||
option?: string;
|
option?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
openAboutAdsModal: {
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeAboutAdsModal: WithTabId | undefined;
|
||||||
openPreviousReportAdModal: WithTabId | undefined;
|
openPreviousReportAdModal: WithTabId | undefined;
|
||||||
openPreviousReportModal: WithTabId | undefined;
|
openPreviousReportModal: WithTabId | undefined;
|
||||||
closeReportAdModal: WithTabId | undefined;
|
closeReportAdModal: WithTabId | undefined;
|
||||||
|
|||||||
3
src/types/language.d.ts
vendored
3
src/types/language.d.ts
vendored
@ -610,7 +610,8 @@ export interface LangPair {
|
|||||||
'ChatPollTotalVotesResultEmpty': undefined;
|
'ChatPollTotalVotesResultEmpty': undefined;
|
||||||
'Vote': undefined;
|
'Vote': undefined;
|
||||||
'MessageRecommendedLabel': undefined;
|
'MessageRecommendedLabel': undefined;
|
||||||
'SponsoredMessage': undefined;
|
'SponsoredMessageAd': undefined;
|
||||||
|
'SponsoredMessageAdWhatIsThis': undefined;
|
||||||
'PremiumStickerTooltip': undefined;
|
'PremiumStickerTooltip': undefined;
|
||||||
'ViewAction': undefined;
|
'ViewAction': undefined;
|
||||||
'Loading': undefined;
|
'Loading': undefined;
|
||||||
|
|||||||
@ -116,10 +116,10 @@ type AdvancedLangFnParameters =
|
|||||||
export type LangFnParameters = RegularLangFnParameters | AdvancedLangFnParameters;
|
export type LangFnParameters = RegularLangFnParameters | AdvancedLangFnParameters;
|
||||||
|
|
||||||
export type LangFn = {
|
export type LangFn = {
|
||||||
<K = RegularLangKey>(
|
<K extends RegularLangKey = RegularLangKey>(
|
||||||
key: K, variables?: undefined, options?: LangFnOptions,
|
key: K, variables?: undefined, options?: LangFnOptions,
|
||||||
): string;
|
): string;
|
||||||
<K = PluralLangKey>(
|
<K extends PluralLangKey = PluralLangKey>(
|
||||||
key: K, variables: undefined, options: LangFnOptionsWithPlural,
|
key: K, variables: undefined, options: LangFnOptionsWithPlural,
|
||||||
): string;
|
): string;
|
||||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||||
@ -129,10 +129,10 @@ export type LangFn = {
|
|||||||
key: K, variables: V, options: LangFnOptionsWithPlural,
|
key: K, variables: V, options: LangFnOptionsWithPlural,
|
||||||
): string;
|
): string;
|
||||||
|
|
||||||
<K = RegularLangKey>(
|
<K extends RegularLangKey = RegularLangKey>(
|
||||||
key: K, variables?: undefined, options?: AdvancedLangFnOptions,
|
key: K, variables?: undefined, options?: AdvancedLangFnOptions,
|
||||||
): TeactNode;
|
): TeactNode;
|
||||||
<K = PluralLangKey>(
|
<K extends PluralLangKey = PluralLangKey>(
|
||||||
key: K, variables: undefined, options: AdvancedLangFnOptionsWithPlural,
|
key: K, variables: undefined, options: AdvancedLangFnOptionsWithPlural,
|
||||||
): TeactNode;
|
): TeactNode;
|
||||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user