MessageContextMenu: Read time in private chats (#4218)
This commit is contained in:
parent
6146cf3a1b
commit
64799a8818
@ -45,6 +45,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
reactions_uniq_max: number;
|
reactions_uniq_max: number;
|
||||||
chat_read_mark_size_threshold: number;
|
chat_read_mark_size_threshold: number;
|
||||||
chat_read_mark_expire_period: number;
|
chat_read_mark_expire_period: number;
|
||||||
|
pm_read_date_expire_period: number;
|
||||||
reactions_user_max_default: number;
|
reactions_user_max_default: number;
|
||||||
reactions_user_max_premium: number;
|
reactions_user_max_premium: number;
|
||||||
autologin_domains: string[];
|
autologin_domains: string[];
|
||||||
@ -98,6 +99,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
emojiSounds: buildEmojiSounds(appConfig),
|
emojiSounds: buildEmojiSounds(appConfig),
|
||||||
seenByMaxChatMembers: appConfig.chat_read_mark_size_threshold,
|
seenByMaxChatMembers: appConfig.chat_read_mark_size_threshold,
|
||||||
seenByExpiresAt: appConfig.chat_read_mark_expire_period,
|
seenByExpiresAt: appConfig.chat_read_mark_expire_period,
|
||||||
|
readDateExpiresAt: appConfig.pm_read_date_expire_period,
|
||||||
autologinDomains: appConfig.autologin_domains || [],
|
autologinDomains: appConfig.autologin_domains || [],
|
||||||
urlAuthDomains: appConfig.url_auth_domains || [],
|
urlAuthDomains: appConfig.url_auth_domains || [],
|
||||||
maxUniqueReactions: appConfig.reactions_uniq_max,
|
maxUniqueReactions: appConfig.reactions_uniq_max,
|
||||||
|
|||||||
@ -108,11 +108,11 @@ export function buildApiUserStatus(mtpStatus?: GramJs.TypeUserStatus): ApiUserSt
|
|||||||
} else if (mtpStatus instanceof GramJs.UserStatusOffline) {
|
} else if (mtpStatus instanceof GramJs.UserStatusOffline) {
|
||||||
return { type: 'userStatusOffline', wasOnline: mtpStatus.wasOnline };
|
return { type: 'userStatusOffline', wasOnline: mtpStatus.wasOnline };
|
||||||
} else if (mtpStatus instanceof GramJs.UserStatusRecently) {
|
} else if (mtpStatus instanceof GramJs.UserStatusRecently) {
|
||||||
return { type: 'userStatusRecently' };
|
return { type: 'userStatusRecently', isReadDateRestrictedByMe: mtpStatus.byMe };
|
||||||
} else if (mtpStatus instanceof GramJs.UserStatusLastWeek) {
|
} else if (mtpStatus instanceof GramJs.UserStatusLastWeek) {
|
||||||
return { type: 'userStatusLastWeek' };
|
return { type: 'userStatusLastWeek', isReadDateRestrictedByMe: mtpStatus.byMe };
|
||||||
} else {
|
} else {
|
||||||
return { type: 'userStatusLastMonth' };
|
return { type: 'userStatusLastMonth', isReadDateRestrictedByMe: mtpStatus.byMe };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export {
|
|||||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
||||||
|
fetchOutboxReadDate,
|
||||||
deleteSavedHistory,
|
deleteSavedHistory,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
|
||||||
|
|||||||
@ -1868,3 +1868,17 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU
|
|||||||
|
|
||||||
handleGramJsUpdate(update);
|
handleGramJsUpdate(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchOutboxReadDate({ chat, messageId }: { chat: ApiChat; messageId: number }) {
|
||||||
|
const { id, accessHash } = chat;
|
||||||
|
const peer = buildInputPeer(id, accessHash);
|
||||||
|
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetOutboxReadDate({
|
||||||
|
peer: peer as GramJs.TypeInputPeer,
|
||||||
|
msgId: messageId,
|
||||||
|
}), { shouldThrow: true });
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
return { date: result.date };
|
||||||
|
}
|
||||||
|
|||||||
@ -614,15 +614,18 @@ export async function fetchGlobalPrivacySettings() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||||
|
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact }: {
|
export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact, shouldHideReadMarks }: {
|
||||||
shouldArchiveAndMuteNewNonContact: boolean;
|
shouldArchiveAndMuteNewNonContact: boolean;
|
||||||
|
shouldHideReadMarks: boolean;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
|
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
|
||||||
settings: new GramJs.GlobalPrivacySettings({
|
settings: new GramJs.GlobalPrivacySettings({
|
||||||
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
|
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
|
||||||
|
...(shouldHideReadMarks && { hideReadMarks: true }),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -632,6 +635,7 @@ export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonCo
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
|
||||||
|
shouldHideReadMarks: Boolean(result.hideReadMarks),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -536,6 +536,7 @@ export interface ApiMessage {
|
|||||||
};
|
};
|
||||||
reactions?: ApiReactions;
|
reactions?: ApiReactions;
|
||||||
hasComments?: boolean;
|
hasComments?: boolean;
|
||||||
|
readDate?: number;
|
||||||
savedPeerId?: string;
|
savedPeerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -178,6 +178,7 @@ export interface ApiAppConfig {
|
|||||||
emojiSounds: Record<string, string>;
|
emojiSounds: Record<string, string>;
|
||||||
seenByMaxChatMembers: number;
|
seenByMaxChatMembers: number;
|
||||||
seenByExpiresAt: number;
|
seenByExpiresAt: number;
|
||||||
|
readDateExpiresAt: number;
|
||||||
autologinDomains: string[];
|
autologinDomains: string[];
|
||||||
urlAuthDomains: string[];
|
urlAuthDomains: string[];
|
||||||
premiumInvoiceSlug: string;
|
premiumInvoiceSlug: string;
|
||||||
|
|||||||
@ -66,6 +66,8 @@ export interface ApiUserStatus {
|
|||||||
);
|
);
|
||||||
wasOnline?: number;
|
wasOnline?: number;
|
||||||
expires?: number;
|
expires?: number;
|
||||||
|
isReadDateRestrictedByMe?: boolean;
|
||||||
|
isReadDateRestricted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUsername {
|
export interface ApiUsername {
|
||||||
|
|||||||
BIN
src/assets/tgs/ReadTime.tgs
Normal file
BIN
src/assets/tgs/ReadTime.tgs
Normal file
Binary file not shown.
@ -30,6 +30,7 @@ export { default as PinMessageModal } from '../components/common/PinMessageModal
|
|||||||
export { default as UnpinAllMessagesModal } from '../components/common/UnpinAllMessagesModal';
|
export { default as UnpinAllMessagesModal } from '../components/common/UnpinAllMessagesModal';
|
||||||
export { default as MessageSelectToolbar } from '../components/middle/MessageSelectToolbar';
|
export { default as MessageSelectToolbar } from '../components/middle/MessageSelectToolbar';
|
||||||
export { default as SeenByModal } from '../components/common/SeenByModal';
|
export { default as SeenByModal } from '../components/common/SeenByModal';
|
||||||
|
export { default as ReadTimeModal } from '../components/common/ReadDateModal';
|
||||||
export { default as ReactorListModal } from '../components/middle/ReactorListModal';
|
export { default as ReactorListModal } from '../components/middle/ReactorListModal';
|
||||||
export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation';
|
export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation';
|
||||||
export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal';
|
export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal';
|
||||||
|
|||||||
43
src/components/common/ReadDateModal.module.scss
Normal file
43
src/components/common/ReadDateModal.module.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin: 0.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
text-transform: none;
|
||||||
|
border-radius: var(--border-radius-default-tiny);
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
106
src/components/common/ReadDateModal.tsx
Normal file
106
src/components/common/ReadDateModal.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import type { ApiUser } from '../../api/types';
|
||||||
|
|
||||||
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
|
import { getUserFirstOrLastName } from '../../global/helpers';
|
||||||
|
import { selectTabState, selectUser } from '../../global/selectors';
|
||||||
|
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||||
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
|
import Button from '../ui/Button';
|
||||||
|
import Modal, { ANIMATION_DURATION } from '../ui/Modal';
|
||||||
|
import Separator from '../ui/Separator';
|
||||||
|
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
|
import styles from './ReadDateModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
user?: ApiUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLOSE_ANIMATION_DURATION = ANIMATION_DURATION + ANIMATION_END_DELAY;
|
||||||
|
|
||||||
|
const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
|
||||||
|
const lang = useLang();
|
||||||
|
const {
|
||||||
|
updateGlobalPrivacySettings, openPremiumModal, closeGetReadDateModal, showNotification,
|
||||||
|
} = getActions();
|
||||||
|
const userName = getUserFirstOrLastName(user);
|
||||||
|
|
||||||
|
const handleShowReadTime = () => {
|
||||||
|
updateGlobalPrivacySettings({ shouldHideReadMarks: false });
|
||||||
|
closeGetReadDateModal();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
showNotification({ message: lang('PremiumReadSet') });
|
||||||
|
}, CLOSE_ANIMATION_DURATION);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenPremium = () => {
|
||||||
|
closeGetReadDateModal();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
openPremiumModal();
|
||||||
|
}, CLOSE_ANIMATION_DURATION);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isSlim isOpen={isOpen} onClose={closeGetReadDateModal}>
|
||||||
|
<div className={styles.container} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
<Button
|
||||||
|
className={styles.closeButton}
|
||||||
|
color="translucent"
|
||||||
|
round
|
||||||
|
size="smaller"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => closeGetReadDateModal()}
|
||||||
|
ariaLabel="Close"
|
||||||
|
>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.ReadTime}
|
||||||
|
size={84}
|
||||||
|
className={styles.icon}
|
||||||
|
nonInteractive
|
||||||
|
noLoop
|
||||||
|
/>
|
||||||
|
<h2 className={styles.header}>{lang('PremiumReadHeader1')}</h2>
|
||||||
|
<p className={styles.desc}>{renderText(lang('PremiumReadText1', userName), ['simple_markdown'])}</p>
|
||||||
|
<Button
|
||||||
|
size="smaller"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={handleShowReadTime}
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
|
{lang('PremiumReadButton1')}
|
||||||
|
</Button>
|
||||||
|
<Separator className={styles.separator}>{lang('PremiumOr')}</Separator>
|
||||||
|
<h2 className={styles.header}>{lang('PremiumReadHeader2')}</h2>
|
||||||
|
<p className={styles.desc}>{renderText(lang('PremiumReadText2', userName), ['simple_markdown'])}</p>
|
||||||
|
{/* eslint-disable-next-line react/jsx-no-bind */}
|
||||||
|
<Button withPremiumGradient size="smaller" onClick={handleOpenPremium} className={styles.button}>
|
||||||
|
{lang('PremiumLastSeenButton2')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
const { chatId } = selectTabState(global).readDateModal || {};
|
||||||
|
const user = chatId ? selectUser(global, chatId) : undefined;
|
||||||
|
|
||||||
|
return { user };
|
||||||
|
},
|
||||||
|
)(ReadDateModal));
|
||||||
18
src/components/common/ReadTimeModal.async.tsx
Normal file
18
src/components/common/ReadTimeModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './ReadDateModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const ReadTimeModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { isOpen } = props;
|
||||||
|
const ReadTimeModal = useModuleLoader(Bundles.Extra, 'ReadTimeModal', !isOpen);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return ReadTimeModal ? <ReadTimeModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReadTimeModalAsync;
|
||||||
@ -15,6 +15,7 @@ import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.t
|
|||||||
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
|
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
|
||||||
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
|
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
|
||||||
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||||
|
import ReadTime from '../../../assets/tgs/ReadTime.tgs';
|
||||||
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
import Congratulations from '../../../assets/tgs/settings/Congratulations.tgs';
|
||||||
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
||||||
import Experimental from '../../../assets/tgs/settings/Experimental.tgs';
|
import Experimental from '../../../assets/tgs/settings/Experimental.tgs';
|
||||||
@ -48,4 +49,5 @@ export const LOCAL_TGS_URLS = {
|
|||||||
Experimental,
|
Experimental,
|
||||||
PartyPopper,
|
PartyPopper,
|
||||||
Flame,
|
Flame,
|
||||||
|
ReadTime,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
:global(.settings-item-description-larger).premiumInfo {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
84
src/components/left/settings/SettingsPrivacyLastSeen.tsx
Normal file
84
src/components/left/settings/SettingsPrivacyLastSeen.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { PrivacyVisibility } from '../../../types';
|
||||||
|
|
||||||
|
import { selectIsCurrentUserPremium, selectShouldHideReadMarks } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import PremiumIcon from '../../common/PremiumIcon';
|
||||||
|
import Checkbox from '../../ui/Checkbox';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
|
||||||
|
import styles from './SettingsPrivacyLastSeen.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
visibility?: PrivacyVisibility;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isCurrentUserPremium: boolean;
|
||||||
|
shouldHideReadMarks: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsPrivacyLastSeen = ({
|
||||||
|
isCurrentUserPremium, shouldHideReadMarks, visibility,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const { updateGlobalPrivacySettings, openPremiumModal } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
const canShowHideReadTime = visibility === 'nobody' || visibility === 'contacts';
|
||||||
|
|
||||||
|
const handleChangeShouldHideReadMarks = useLastCallback(
|
||||||
|
(isEnabled) => updateGlobalPrivacySettings({ shouldHideReadMarks: isEnabled }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{canShowHideReadTime && (
|
||||||
|
<div className="settings-item">
|
||||||
|
<Checkbox
|
||||||
|
label={lang('HideReadTime')}
|
||||||
|
checked={shouldHideReadMarks}
|
||||||
|
onCheck={handleChangeShouldHideReadMarks}
|
||||||
|
/>
|
||||||
|
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{renderText(lang('HideReadTimeInfo'), ['br'])}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="settings-item">
|
||||||
|
<ListItem
|
||||||
|
leftElement={<PremiumIcon className="icon" withGradient big />}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => openPremiumModal()}
|
||||||
|
>
|
||||||
|
{isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}
|
||||||
|
</ListItem>
|
||||||
|
<p
|
||||||
|
className={buildClassName(
|
||||||
|
'settings-item-description-larger',
|
||||||
|
styles.premiumInfo,
|
||||||
|
)}
|
||||||
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
|
>
|
||||||
|
{isCurrentUserPremium
|
||||||
|
? lang('PrivacyLastSeenPremiumInfoForPremium')
|
||||||
|
: lang('PrivacyLastSeenPremiumInfo')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
return {
|
||||||
|
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||||
|
shouldHideReadMarks: Boolean(selectShouldHideReadMarks(global)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SettingsPrivacyLastSeen));
|
||||||
@ -15,6 +15,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
|||||||
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
|
import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen';
|
||||||
import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto';
|
import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
@ -74,6 +75,9 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
|||||||
currentUserFallbackPhoto={currentUserFallbackPhoto}
|
currentUserFallbackPhoto={currentUserFallbackPhoto}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{screen === SettingsScreens.PrivacyLastSeen && (
|
||||||
|
<SettingsPrivacyLastSeen visibility={primaryPrivacy?.visibility} />
|
||||||
|
)}
|
||||||
{secondaryScreen && (
|
{secondaryScreen && (
|
||||||
<PrivacySubsection
|
<PrivacySubsection
|
||||||
screen={secondaryScreen}
|
screen={secondaryScreen}
|
||||||
|
|||||||
@ -78,6 +78,7 @@ import useWindowSize from '../../hooks/window/useWindowSize';
|
|||||||
import usePinnedMessage from './hooks/usePinnedMessage';
|
import usePinnedMessage from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import Composer from '../common/Composer';
|
import Composer from '../common/Composer';
|
||||||
|
import ReadTimeModal from '../common/ReadTimeModal.async';
|
||||||
import SeenByModal from '../common/SeenByModal.async';
|
import SeenByModal from '../common/SeenByModal.async';
|
||||||
import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async';
|
import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async';
|
||||||
import GiftPremiumModal from '../main/premium/GiftPremiumModal.async';
|
import GiftPremiumModal from '../main/premium/GiftPremiumModal.async';
|
||||||
@ -128,6 +129,7 @@ type StateProps = {
|
|||||||
hasCurrentTextSearch?: boolean;
|
hasCurrentTextSearch?: boolean;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
isSeenByModalOpen: boolean;
|
isSeenByModalOpen: boolean;
|
||||||
|
isReadDateModalOpen: boolean;
|
||||||
isReactorListModalOpen: boolean;
|
isReactorListModalOpen: boolean;
|
||||||
isGiftPremiumModalOpen?: boolean;
|
isGiftPremiumModalOpen?: boolean;
|
||||||
isChatLanguageModalOpen?: boolean;
|
isChatLanguageModalOpen?: boolean;
|
||||||
@ -186,6 +188,7 @@ function MiddleColumn({
|
|||||||
hasCurrentTextSearch,
|
hasCurrentTextSearch,
|
||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
isSeenByModalOpen,
|
isSeenByModalOpen,
|
||||||
|
isReadDateModalOpen,
|
||||||
isReactorListModalOpen,
|
isReactorListModalOpen,
|
||||||
isGiftPremiumModalOpen,
|
isGiftPremiumModalOpen,
|
||||||
isChatLanguageModalOpen,
|
isChatLanguageModalOpen,
|
||||||
@ -676,6 +679,7 @@ function MiddleColumn({
|
|||||||
canPost={renderingCanPost}
|
canPost={renderingCanPost}
|
||||||
/>
|
/>
|
||||||
<SeenByModal isOpen={isSeenByModalOpen} />
|
<SeenByModal isOpen={isSeenByModalOpen} />
|
||||||
|
<ReadTimeModal isOpen={isReadDateModalOpen} />
|
||||||
<ReactorListModal isOpen={isReactorListModalOpen} />
|
<ReactorListModal isOpen={isReactorListModalOpen} />
|
||||||
{IS_TRANSLATION_SUPPORTED && <ChatLanguageModal isOpen={isChatLanguageModalOpen} />}
|
{IS_TRANSLATION_SUPPORTED && <ChatLanguageModal isOpen={isChatLanguageModalOpen} />}
|
||||||
</div>
|
</div>
|
||||||
@ -723,7 +727,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const {
|
const {
|
||||||
messageLists, isLeftColumnShown, activeEmojiInteractions,
|
messageLists, isLeftColumnShown, activeEmojiInteractions,
|
||||||
seenByModal, giftPremiumModal, reactorModal, audioPlayer, shouldSkipHistoryAnimations,
|
seenByModal, giftPremiumModal, reactorModal, audioPlayer, shouldSkipHistoryAnimations,
|
||||||
chatLanguageModal,
|
chatLanguageModal, readDateModal,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
const { leftColumnWidth } = global;
|
const { leftColumnWidth } = global;
|
||||||
@ -739,6 +743,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
hasCurrentTextSearch: Boolean(selectCurrentTextSearch(global)),
|
hasCurrentTextSearch: Boolean(selectCurrentTextSearch(global)),
|
||||||
isSelectModeActive: selectIsInSelectMode(global),
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
isSeenByModalOpen: Boolean(seenByModal),
|
isSeenByModalOpen: Boolean(seenByModal),
|
||||||
|
isReadDateModalOpen: Boolean(readDateModal),
|
||||||
isReactorListModalOpen: Boolean(reactorModal),
|
isReactorListModalOpen: Boolean(reactorModal),
|
||||||
isGiftPremiumModalOpen: giftPremiumModal?.isOpen,
|
isGiftPremiumModalOpen: giftPremiumModal?.isOpen,
|
||||||
isChatLanguageModalOpen: Boolean(chatLanguageModal),
|
isChatLanguageModalOpen: Boolean(chatLanguageModal),
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectIsMessageProtected,
|
selectIsMessageProtected,
|
||||||
|
selectIsMessageUnread,
|
||||||
selectIsPremiumPurchaseBlocked,
|
selectIsPremiumPurchaseBlocked,
|
||||||
selectIsReactionPickerOpen,
|
selectIsReactionPickerOpen,
|
||||||
selectMessageCustomEmojiSets,
|
selectMessageCustomEmojiSets,
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
selectRequestedMessageTranslationLanguage,
|
selectRequestedMessageTranslationLanguage,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
|
selectUserStatus,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
@ -107,6 +109,8 @@ type StateProps = {
|
|||||||
canSaveGif?: boolean;
|
canSaveGif?: boolean;
|
||||||
canRevote?: boolean;
|
canRevote?: boolean;
|
||||||
canClosePoll?: boolean;
|
canClosePoll?: boolean;
|
||||||
|
canLoadReadDate?: boolean;
|
||||||
|
shouldRenderShowWhen?: boolean;
|
||||||
activeDownloads?: TabState['activeDownloads']['byChatId'][number];
|
activeDownloads?: TabState['activeDownloads']['byChatId'][number];
|
||||||
canShowSeenBy?: boolean;
|
canShowSeenBy?: boolean;
|
||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
@ -159,6 +163,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canRevote,
|
canRevote,
|
||||||
canClosePoll,
|
canClosePoll,
|
||||||
canPlayAnimatedEmojis,
|
canPlayAnimatedEmojis,
|
||||||
|
canLoadReadDate,
|
||||||
|
shouldRenderShowWhen,
|
||||||
activeDownloads,
|
activeDownloads,
|
||||||
noReplies,
|
noReplies,
|
||||||
canShowSeenBy,
|
canShowSeenBy,
|
||||||
@ -200,6 +206,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
showOriginalMessage,
|
showOriginalMessage,
|
||||||
openChatLanguageModal,
|
openChatLanguageModal,
|
||||||
openMessageReactionPicker,
|
openMessageReactionPicker,
|
||||||
|
loadOutboxReadDate,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -221,6 +228,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [loadSeenBy, isOpen, message.chatId, message.id, canShowSeenBy]);
|
}, [loadSeenBy, isOpen, message.chatId, message.id, canShowSeenBy]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (canLoadReadDate && isOpen) {
|
||||||
|
loadOutboxReadDate({ chatId: message.chatId, messageId: message.id });
|
||||||
|
}
|
||||||
|
}, [canLoadReadDate, isOpen, message.chatId, message.id, message.readDate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canShowReactionsCount && isOpen) {
|
if (canShowReactionsCount && isOpen) {
|
||||||
loadReactors({ chatId: message.chatId, messageId: message.id });
|
loadReactors({ chatId: message.chatId, messageId: message.id });
|
||||||
@ -554,6 +567,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
canShowOriginal={canShowOriginal}
|
canShowOriginal={canShowOriginal}
|
||||||
canSelectLanguage={canSelectLanguage}
|
canSelectLanguage={canSelectLanguage}
|
||||||
canPlayAnimatedEmojis={canPlayAnimatedEmojis}
|
canPlayAnimatedEmojis={canPlayAnimatedEmojis}
|
||||||
|
shouldRenderShowWhen={shouldRenderShowWhen}
|
||||||
|
canLoadReadDate={canLoadReadDate}
|
||||||
hasCustomEmoji={hasCustomEmoji}
|
hasCustomEmoji={hasCustomEmoji}
|
||||||
customEmojiSets={customEmojiSets}
|
customEmojiSets={customEmojiSets}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
@ -623,7 +638,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { threadId } = selectCurrentMessageList(global) || {};
|
const { threadId } = selectCurrentMessageList(global) || {};
|
||||||
const activeDownloads = selectActiveDownloads(global, message.chatId);
|
const activeDownloads = selectActiveDownloads(global, message.chatId);
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
const { seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions } = global.appConfig || {};
|
const {
|
||||||
|
seenByExpiresAt, seenByMaxChatMembers, maxUniqueReactions, readDateExpiresAt,
|
||||||
|
} = global.appConfig || {};
|
||||||
const {
|
const {
|
||||||
noOptions,
|
noOptions,
|
||||||
canReply,
|
canReply,
|
||||||
@ -645,7 +662,20 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = (threadId && selectAllowedMessageActions(global, message, threadId)) || {};
|
} = (threadId && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||||
|
|
||||||
const isPrivate = chat && isUserId(chat.id);
|
const isPrivate = chat && isUserId(chat.id);
|
||||||
|
const userStatus = isPrivate ? selectUserStatus(global, chat.id) : undefined;
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
|
const isMessageUnread = selectIsMessageUnread(global, message);
|
||||||
|
const canLoadReadDate = Boolean(
|
||||||
|
isPrivate
|
||||||
|
&& isOwn
|
||||||
|
&& !isMessageUnread
|
||||||
|
&& readDateExpiresAt
|
||||||
|
&& message.date > Date.now() / 1000 - readDateExpiresAt
|
||||||
|
&& !userStatus?.isReadDateRestricted,
|
||||||
|
);
|
||||||
|
const shouldRenderShowWhen = Boolean(
|
||||||
|
canLoadReadDate && isPrivate && selectUserStatus(global, chat.id)?.isReadDateRestrictedByMe,
|
||||||
|
);
|
||||||
const isPinned = messageListType === 'pinned';
|
const isPinned = messageListType === 'pinned';
|
||||||
const isScheduled = messageListType === 'scheduled';
|
const isScheduled = messageListType === 'scheduled';
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
@ -653,6 +683,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const hasTtl = hasMessageTtl(message);
|
const hasTtl = hasMessageTtl(message);
|
||||||
const canShowSeenBy = Boolean(!isLocal
|
const canShowSeenBy = Boolean(!isLocal
|
||||||
&& chat
|
&& chat
|
||||||
|
&& !isMessageUnread
|
||||||
&& seenByMaxChatMembers
|
&& seenByMaxChatMembers
|
||||||
&& seenByExpiresAt
|
&& seenByExpiresAt
|
||||||
&& isChatGroup(chat)
|
&& isChatGroup(chat)
|
||||||
@ -706,6 +737,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canClosePoll: !isScheduled && canClosePoll,
|
canClosePoll: !isScheduled && canClosePoll,
|
||||||
activeDownloads,
|
activeDownloads,
|
||||||
canShowSeenBy,
|
canShowSeenBy,
|
||||||
|
canLoadReadDate,
|
||||||
|
shouldRenderShowWhen,
|
||||||
enabledReactions: chat?.isForbidden ? undefined : chatFullInfo?.enabledReactions,
|
enabledReactions: chat?.isForbidden ? undefined : chatFullInfo?.enabledReactions,
|
||||||
maxUniqueReactions,
|
maxUniqueReactions,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
|||||||
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
import Separator from '../../ui/Separator';
|
||||||
|
|
||||||
import styles from './Giveaway.module.scss';
|
import styles from './Giveaway.module.scss';
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ const Giveaway = ({
|
|||||||
['simple_markdown'],
|
['simple_markdown'],
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.separator}>{lang('BoostingGiveawayMsgWithDivider')}</div>
|
<Separator>{lang('BoostingGiveawayMsgWithDivider')}</Separator>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<p className={styles.description}>
|
<p className={styles.description}>
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import MenuItem from '../../ui/MenuItem';
|
|||||||
import MenuSeparator from '../../ui/MenuSeparator';
|
import MenuSeparator from '../../ui/MenuSeparator';
|
||||||
import Skeleton from '../../ui/placeholder/Skeleton';
|
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||||
import ReactionSelector from './ReactionSelector';
|
import ReactionSelector from './ReactionSelector';
|
||||||
|
import ReadTimeMenuItem from './ReadTimeMenuItem';
|
||||||
|
|
||||||
import './MessageContextMenu.scss';
|
import './MessageContextMenu.scss';
|
||||||
|
|
||||||
@ -86,6 +87,8 @@ type OwnProps = {
|
|||||||
customEmojiSets?: ApiStickerSet[];
|
customEmojiSets?: ApiStickerSet[];
|
||||||
canPlayAnimatedEmojis?: boolean;
|
canPlayAnimatedEmojis?: boolean;
|
||||||
noTransition?: boolean;
|
noTransition?: boolean;
|
||||||
|
shouldRenderShowWhen?: boolean;
|
||||||
|
canLoadReadDate?: boolean;
|
||||||
onReply?: NoneToVoidFunction;
|
onReply?: NoneToVoidFunction;
|
||||||
onOpenThread?: VoidFunction;
|
onOpenThread?: VoidFunction;
|
||||||
onEdit?: NoneToVoidFunction;
|
onEdit?: NoneToVoidFunction;
|
||||||
@ -171,6 +174,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
customEmojiSets,
|
customEmojiSets,
|
||||||
canPlayAnimatedEmojis,
|
canPlayAnimatedEmojis,
|
||||||
noTransition,
|
noTransition,
|
||||||
|
shouldRenderShowWhen,
|
||||||
|
canLoadReadDate,
|
||||||
onReply,
|
onReply,
|
||||||
onOpenThread,
|
onOpenThread,
|
||||||
onEdit,
|
onEdit,
|
||||||
@ -401,42 +406,10 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
{canForward && <MenuItem icon="forward" onClick={onForward}>{lang('Forward')}</MenuItem>}
|
{canForward && <MenuItem icon="forward" onClick={onForward}>{lang('Forward')}</MenuItem>}
|
||||||
{canSelect && <MenuItem icon="select" onClick={onSelect}>{lang('Common.Select')}</MenuItem>}
|
{canSelect && <MenuItem icon="select" onClick={onSelect}>{lang('Common.Select')}</MenuItem>}
|
||||||
{canReport && <MenuItem icon="flag" onClick={onReport}>{lang('lng_context_report_msg')}</MenuItem>}
|
{canReport && <MenuItem icon="flag" onClick={onReport}>{lang('lng_context_report_msg')}</MenuItem>}
|
||||||
{(canShowSeenBy || canShowReactionsCount) && !isSponsoredMessage && (
|
|
||||||
<MenuItem
|
|
||||||
icon={canShowReactionsCount ? 'heart-outline' : 'group'}
|
|
||||||
onClick={canShowReactionsCount ? onShowReactors : onShowSeenBy}
|
|
||||||
disabled={!canShowReactionsCount && !seenByDatesCount}
|
|
||||||
>
|
|
||||||
<span className="MessageContextMenu--seen-by-label-wrapper">
|
|
||||||
<span className="MessageContextMenu--seen-by-label" dir={lang.isRtl ? 'rtl' : undefined}>
|
|
||||||
{canShowReactionsCount && message.reactors?.count ? (
|
|
||||||
canShowSeenBy && seenByDatesCount
|
|
||||||
? lang(
|
|
||||||
'Chat.OutgoingContextMixedReactionCount',
|
|
||||||
[message.reactors.count, seenByDatesCount],
|
|
||||||
)
|
|
||||||
: lang('Chat.ContextReactionCount', message.reactors.count, 'i')
|
|
||||||
) : (
|
|
||||||
seenByDatesCount === 1 && seenByRecentPeers
|
|
||||||
? renderText(
|
|
||||||
isUserId(seenByRecentPeers[0].id)
|
|
||||||
? getUserFullName(seenByRecentPeers[0] as ApiUser)!
|
|
||||||
: (seenByRecentPeers[0] as ApiChat).title,
|
|
||||||
) : (
|
|
||||||
seenByDatesCount
|
|
||||||
? lang('Conversation.ContextMenuSeen', seenByDatesCount, 'i')
|
|
||||||
: lang('Conversation.ContextMenuNoViews')
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<AvatarList className="avatars" size="micro" peers={seenByRecentPeers} />
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{canDelete && <MenuItem destructive icon="delete" onClick={onDelete}>{lang('Delete')}</MenuItem>}
|
{canDelete && <MenuItem destructive icon="delete" onClick={onDelete}>{lang('Delete')}</MenuItem>}
|
||||||
{hasCustomEmoji && (
|
{hasCustomEmoji && (
|
||||||
<>
|
<>
|
||||||
<MenuSeparator />
|
<MenuSeparator size="thick" />
|
||||||
{!customEmojiSets && (
|
{!customEmojiSets && (
|
||||||
<>
|
<>
|
||||||
<Skeleton inline className="menu-loading-row" />
|
<Skeleton inline className="menu-loading-row" />
|
||||||
@ -462,6 +435,50 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
{isSponsoredMessage && onSponsoredHide && (
|
{isSponsoredMessage && onSponsoredHide && (
|
||||||
<MenuItem icon="stop" onClick={onSponsoredHide}>{lang('HideAd')}</MenuItem>
|
<MenuItem icon="stop" onClick={onSponsoredHide}>{lang('HideAd')}</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{(canShowSeenBy || canShowReactionsCount) && !isSponsoredMessage && (
|
||||||
|
<>
|
||||||
|
<MenuSeparator size={hasCustomEmoji ? 'thin' : 'thick'} />
|
||||||
|
<MenuItem
|
||||||
|
icon={canShowReactionsCount ? 'heart-outline' : 'group'}
|
||||||
|
onClick={canShowReactionsCount ? onShowReactors : onShowSeenBy}
|
||||||
|
disabled={!canShowReactionsCount && !seenByDatesCount}
|
||||||
|
>
|
||||||
|
<span className="MessageContextMenu--seen-by-label-wrapper">
|
||||||
|
<span className="MessageContextMenu--seen-by-label" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{canShowReactionsCount && message.reactors?.count ? (
|
||||||
|
canShowSeenBy && seenByDatesCount
|
||||||
|
? lang(
|
||||||
|
'Chat.OutgoingContextMixedReactionCount',
|
||||||
|
[message.reactors.count, seenByDatesCount],
|
||||||
|
)
|
||||||
|
: lang('Chat.ContextReactionCount', message.reactors.count, 'i')
|
||||||
|
) : (
|
||||||
|
seenByDatesCount === 1 && seenByRecentPeers
|
||||||
|
? renderText(
|
||||||
|
isUserId(seenByRecentPeers[0].id)
|
||||||
|
? getUserFullName(seenByRecentPeers[0] as ApiUser)!
|
||||||
|
: (seenByRecentPeers[0] as ApiChat).title,
|
||||||
|
) : (
|
||||||
|
seenByDatesCount
|
||||||
|
? lang('Conversation.ContextMenuSeen', seenByDatesCount, 'i')
|
||||||
|
: lang('Conversation.ContextMenuNoViews')
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<AvatarList className="avatars" size="micro" peers={seenByRecentPeers} />
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isSponsoredMessage && (canLoadReadDate || shouldRenderShowWhen) && (
|
||||||
|
<ReadTimeMenuItem
|
||||||
|
canLoadReadDate={canLoadReadDate}
|
||||||
|
shouldRenderShowWhen={shouldRenderShowWhen}
|
||||||
|
message={message}
|
||||||
|
menuSeparatorSize={hasCustomEmoji ? 'thin' : 'thick'}
|
||||||
|
closeContextMenu={onClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|||||||
44
src/components/middle/message/ReadTimeMenuItem.module.scss
Normal file
44
src/components/middle/message/ReadTimeMenuItem.module.scss
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
:global(.MenuItem).item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
cursor: var(--custom-cursor, default);
|
||||||
|
pointer-events: none;
|
||||||
|
--color-skeleton-background: #2121211a;
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.icon) {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[dir="rtl"] {
|
||||||
|
:global(.icon) {
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.get {
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
margin-left: 0.375rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
background: var(--color-background-menu-separator);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
height: 0.5rem;
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
62
src/components/middle/message/ReadTimeMenuItem.tsx
Normal file
62
src/components/middle/message/ReadTimeMenuItem.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import type { ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
|
import { formatDateAtTime } from '../../../util/dateFormat';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import MenuSeparator from '../../ui/MenuSeparator';
|
||||||
|
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||||
|
import Transition from '../../ui/Transition';
|
||||||
|
|
||||||
|
import styles from './ReadTimeMenuItem.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
message: ApiMessage;
|
||||||
|
shouldRenderShowWhen?: boolean;
|
||||||
|
canLoadReadDate?: boolean;
|
||||||
|
menuSeparatorSize: 'thin' | 'thick';
|
||||||
|
closeContextMenu: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ReadTimeMenuItem({
|
||||||
|
message, shouldRenderShowWhen, canLoadReadDate, closeContextMenu, menuSeparatorSize,
|
||||||
|
}: OwnProps) {
|
||||||
|
const { openGetReadDateModal } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
const { readDate } = message;
|
||||||
|
const shouldRenderSkeleton = canLoadReadDate && !readDate && !shouldRenderShowWhen;
|
||||||
|
|
||||||
|
const handleOpenModal = () => {
|
||||||
|
closeContextMenu();
|
||||||
|
openGetReadDateModal({ chatId: message.chatId, messageId: message.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuSeparator size={menuSeparatorSize} />
|
||||||
|
<MenuItem icon="message-read" className={styles.item}>
|
||||||
|
<Transition name="fade" activeKey={shouldRenderSkeleton ? 1 : 2} className={styles.transition}>
|
||||||
|
{shouldRenderSkeleton ? <Skeleton className={styles.skeleton} /> : (
|
||||||
|
<>
|
||||||
|
{Boolean(readDate) && lang('PmReadAt', formatDateAtTime(lang, readDate * 1000))}
|
||||||
|
{!readDate && shouldRenderShowWhen && (
|
||||||
|
<div>
|
||||||
|
{lang('PmRead')}
|
||||||
|
<span className={styles.get} onClick={handleOpenModal}>
|
||||||
|
{lang('PmReadShowWhen')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ReadTimeMenuItem);
|
||||||
@ -1,6 +1,13 @@
|
|||||||
.root {
|
.root {
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
height: 1px;
|
border-radius: 0.0625rem;
|
||||||
border-radius: 1px;
|
background-color: var(--color-background-menu-separator);
|
||||||
background-color: var(--color-interactive-inactive);
|
}
|
||||||
|
|
||||||
|
.thin {
|
||||||
|
height: 0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thick {
|
||||||
|
height: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,12 @@ import styles from './MenuSeparator.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
size?: 'thin' | 'thick';
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuSeparator: FC<OwnProps> = ({ className }) => {
|
const MenuSeparator: FC<OwnProps> = ({ className, size = 'thin' }) => {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, className)} />
|
<div className={buildClassName(styles.root, styles[size], className)} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import Portal from './Portal';
|
|||||||
|
|
||||||
import './Modal.scss';
|
import './Modal.scss';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
export const ANIMATION_DURATION = 200;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
title?: string | TextPart[];
|
title?: string | TextPart[];
|
||||||
|
|||||||
33
src/components/ui/Separator.module.scss
Normal file
33
src/components/ui/Separator.module.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.separator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 0.0625rem solid var(--color-dividers);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::before {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::after {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[dir="rtl"] {
|
||||||
|
&:not(:empty)::before {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:empty)::after {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/components/ui/Separator.tsx
Normal file
27
src/components/ui/Separator.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
|
import styles from './Separator.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Separator({ children, className }: OwnProps) {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
|
className={buildClassName(styles.separator, className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Separator;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
|
ApiError,
|
||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
ApiInputReplyInfo,
|
ApiInputReplyInfo,
|
||||||
ApiInputStoryReplyInfo,
|
ApiInputStoryReplyInfo,
|
||||||
@ -66,6 +67,7 @@ import {
|
|||||||
replaceScheduledMessages,
|
replaceScheduledMessages,
|
||||||
replaceSettings,
|
replaceSettings,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
|
replaceUserStatuses,
|
||||||
safeReplacePinnedIds,
|
safeReplacePinnedIds,
|
||||||
safeReplaceViewportIds,
|
safeReplaceViewportIds,
|
||||||
updateChat,
|
updateChat,
|
||||||
@ -116,6 +118,7 @@ import {
|
|||||||
selectTranslationLanguage,
|
selectTranslationLanguage,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
|
selectUserStatus,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { deleteMessages } from '../apiUpdaters/messages';
|
import { deleteMessages } from '../apiUpdaters/messages';
|
||||||
@ -1820,6 +1823,44 @@ addActionHandler('loadMessageViews', async (global, actions, payload): Promise<v
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadOutboxReadDate', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, messageId } = payload;
|
||||||
|
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await callApi('fetchOutboxReadDate', { chat, messageId });
|
||||||
|
if (result?.date) {
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateChatMessage(global, chatId, messageId, { readDate: result.date });
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const { message } = error as ApiError;
|
||||||
|
|
||||||
|
if (message === 'USER_PRIVACY_RESTRICTED' || message === 'YOUR_PRIVACY_RESTRICTED') {
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
const user = selectUser(global, chatId);
|
||||||
|
if (!user) return;
|
||||||
|
const userStatus = selectUserStatus(global, chatId);
|
||||||
|
if (!userStatus) return;
|
||||||
|
|
||||||
|
const updateStatus = message === 'USER_PRIVACY_RESTRICTED'
|
||||||
|
? { isReadDateRestricted: true }
|
||||||
|
: { isReadDateRestrictedByMe: true };
|
||||||
|
|
||||||
|
global = replaceUserStatuses(global, {
|
||||||
|
[chatId]: { ...userStatus, ...updateStatus },
|
||||||
|
});
|
||||||
|
// Need to reset `readDate` to `undefined` after click on "Show my Read Time" button
|
||||||
|
global = updateChatMessage(global, chatId, messageId, { readDate: undefined });
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function countSortedIds(ids: number[], from: number, to: number) {
|
function countSortedIds(ids: number[], from: number, to: number) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
|
|||||||
@ -666,24 +666,29 @@ addActionHandler('loadGlobalPrivacySettings', async (global): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = replaceSettings(global, {
|
global = replaceSettings(global, { ...globalSettings });
|
||||||
shouldArchiveAndMuteNewNonContact: globalSettings.shouldArchiveAndMuteNewNonContact,
|
|
||||||
});
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload): Promise<void> => {
|
addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload): Promise<void> => {
|
||||||
const { shouldArchiveAndMuteNewNonContact } = payload;
|
const shouldArchiveAndMuteNewNonContact = payload.shouldArchiveAndMuteNewNonContact
|
||||||
global = replaceSettings(global, { shouldArchiveAndMuteNewNonContact });
|
?? Boolean(global.settings.byKey.shouldArchiveAndMuteNewNonContact);
|
||||||
|
const shouldHideReadMarks = payload.shouldHideReadMarks ?? Boolean(global.settings.byKey.shouldHideReadMarks);
|
||||||
|
|
||||||
|
global = replaceSettings(global, { shouldArchiveAndMuteNewNonContact, shouldHideReadMarks });
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const result = await callApi('updateGlobalPrivacySettings', { shouldArchiveAndMuteNewNonContact });
|
const result = await callApi('updateGlobalPrivacySettings', {
|
||||||
|
shouldArchiveAndMuteNewNonContact,
|
||||||
|
shouldHideReadMarks,
|
||||||
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = replaceSettings(global, {
|
global = replaceSettings(global, {
|
||||||
shouldArchiveAndMuteNewNonContact: !result
|
shouldArchiveAndMuteNewNonContact: !result
|
||||||
? !shouldArchiveAndMuteNewNonContact
|
? !shouldArchiveAndMuteNewNonContact
|
||||||
: result.shouldArchiveAndMuteNewNonContact,
|
: result.shouldArchiveAndMuteNewNonContact,
|
||||||
|
shouldHideReadMarks: !result ? !shouldHideReadMarks : result.shouldHideReadMarks,
|
||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -781,6 +781,22 @@ addActionHandler('closeSeenByModal', (global, actions, payload): ActionReturnTyp
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openGetReadDateModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
readDateModal: { chatId, messageId },
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeGetReadDateModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
readDateModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openChatLanguageModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatLanguageModal', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
|||||||
@ -237,6 +237,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
wasTimeFormatSetManually: false,
|
wasTimeFormatSetManually: false,
|
||||||
isConnectionStatusMinimized: true,
|
isConnectionStatusMinimized: true,
|
||||||
shouldArchiveAndMuteNewNonContact: false,
|
shouldArchiveAndMuteNewNonContact: false,
|
||||||
|
shouldHideReadMarks: false,
|
||||||
canTranslate: false,
|
canTranslate: false,
|
||||||
canTranslateChats: true,
|
canTranslateChats: true,
|
||||||
doNotTranslate: [],
|
doNotTranslate: [],
|
||||||
|
|||||||
@ -19,3 +19,7 @@ export function selectCanSetPasscode<T extends GlobalState>(global: T) {
|
|||||||
export function selectTranslationLanguage<T extends GlobalState>(global: T) {
|
export function selectTranslationLanguage<T extends GlobalState>(global: T) {
|
||||||
return global.settings.byKey.translationLanguage || selectLanguageCode(global);
|
return global.settings.byKey.translationLanguage || selectLanguageCode(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectShouldHideReadMarks<T extends GlobalState>(global: T) {
|
||||||
|
return global.settings.byKey.shouldHideReadMarks;
|
||||||
|
}
|
||||||
|
|||||||
@ -283,6 +283,11 @@ export type TabState = {
|
|||||||
messageId: number;
|
messageId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
readDateModal?: {
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
};
|
||||||
|
|
||||||
reactorModal?: {
|
reactorModal?: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
@ -1607,6 +1612,11 @@ export interface ActionPayloads {
|
|||||||
messageId: number;
|
messageId: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeSeenByModal: WithTabId | undefined;
|
closeSeenByModal: WithTabId | undefined;
|
||||||
|
openGetReadDateModal: {
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
} & WithTabId;
|
||||||
|
closeGetReadDateModal: WithTabId | undefined;
|
||||||
closeReactorListModal: WithTabId | undefined;
|
closeReactorListModal: WithTabId | undefined;
|
||||||
openReactorListModal: {
|
openReactorListModal: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -2009,6 +2019,10 @@ export interface ActionPayloads {
|
|||||||
ids: number[];
|
ids: number[];
|
||||||
shouldIncrement?: boolean;
|
shouldIncrement?: boolean;
|
||||||
};
|
};
|
||||||
|
loadOutboxReadDate: {
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
};
|
||||||
animateUnreadReaction: {
|
animateUnreadReaction: {
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -2833,7 +2847,7 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeShareChatFolderModal: undefined | WithTabId;
|
closeShareChatFolderModal: undefined | WithTabId;
|
||||||
loadGlobalPrivacySettings: undefined;
|
loadGlobalPrivacySettings: undefined;
|
||||||
updateGlobalPrivacySettings: { shouldArchiveAndMuteNewNonContact: boolean };
|
updateGlobalPrivacySettings: { shouldArchiveAndMuteNewNonContact?: boolean; shouldHideReadMarks?: boolean };
|
||||||
|
|
||||||
// Premium
|
// Premium
|
||||||
openPremiumModal: ({
|
openPremiumModal: ({
|
||||||
|
|||||||
@ -1412,6 +1412,7 @@ messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int a
|
|||||||
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
|
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
|
||||||
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
|
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
|
||||||
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
||||||
|
messages.getOutboxReadDate#8c4bfe5d peer:InputPeer msg_id:int = OutboxReadDate;
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||||
|
|||||||
@ -168,6 +168,7 @@
|
|||||||
"messages.getBotApp",
|
"messages.getBotApp",
|
||||||
"messages.requestAppWebView",
|
"messages.requestAppWebView",
|
||||||
"messages.togglePeerTranslations",
|
"messages.togglePeerTranslations",
|
||||||
|
"messages.getOutboxReadDate",
|
||||||
"updates.getState",
|
"updates.getState",
|
||||||
"updates.getDifference",
|
"updates.getDifference",
|
||||||
"updates.getChannelDifference",
|
"updates.getChannelDifference",
|
||||||
|
|||||||
@ -62,6 +62,7 @@ $color-message-story-mention-to: #74bcff;
|
|||||||
--color-background-compact-menu: #FFFFFFBB;
|
--color-background-compact-menu: #FFFFFFBB;
|
||||||
--color-background-compact-menu-reactions: #FFFFFFEB;
|
--color-background-compact-menu-reactions: #FFFFFFEB;
|
||||||
--color-background-compact-menu-hover: #000000B2;
|
--color-background-compact-menu-hover: #000000B2;
|
||||||
|
--color-background-menu-separator: #0000001a;
|
||||||
--color-background-selected: #f4f4f5;
|
--color-background-selected: #f4f4f5;
|
||||||
--color-background-secondary: #f4f4f5;
|
--color-background-secondary: #f4f4f5;
|
||||||
--color-background-secondary-accent: #e4e4e5;
|
--color-background-secondary-accent: #e4e4e5;
|
||||||
|
|||||||
@ -324,6 +324,7 @@ body:not(.is-ios) {
|
|||||||
--color-background-compact-menu: rgb(33, 33, 33, 0.867);
|
--color-background-compact-menu: rgb(33, 33, 33, 0.867);
|
||||||
--color-background-compact-menu-reactions: rgb(33, 33, 33, 0.867);
|
--color-background-compact-menu-reactions: rgb(33, 33, 33, 0.867);
|
||||||
--color-background-compact-menu-hover: rgb(0, 0, 0, 0.4);
|
--color-background-compact-menu-hover: rgb(0, 0, 0, 0.4);
|
||||||
|
--color-background-menu-separator: rgba(255, 255, 255, 0.102);
|
||||||
--color-background-secondary: rgb(15, 15, 15);
|
--color-background-secondary: rgb(15, 15, 15);
|
||||||
--color-background-secondary-accent: rgb(16, 15, 16);
|
--color-background-secondary-accent: rgb(16, 15, 16);
|
||||||
--color-background-own: rgb(118, 106, 200);
|
--color-background-own: rgb(118, 106, 200);
|
||||||
|
|||||||
@ -66,5 +66,6 @@
|
|||||||
"--color-forum-unread-topic-hover": ["#e9e9e9", "#363636"],
|
"--color-forum-unread-topic-hover": ["#e9e9e9", "#363636"],
|
||||||
"--color-forum-hover-unread-topic-hover": ["#e2e2e2", "#3f3f3f"],
|
"--color-forum-hover-unread-topic-hover": ["#e2e2e2", "#3f3f3f"],
|
||||||
"--color-chat-username": ["#3C7EB0", "#E9EEF4"],
|
"--color-chat-username": ["#3C7EB0", "#E9EEF4"],
|
||||||
"--color-borders-read-story": ["#C4C9CC", "#737373"]
|
"--color-borders-read-story": ["#C4C9CC", "#737373"],
|
||||||
|
"--color-background-menu-separator": ["#0000001a", "#ffffff1a"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
|
|||||||
wasTimeFormatSetManually: boolean;
|
wasTimeFormatSetManually: boolean;
|
||||||
isConnectionStatusMinimized: boolean;
|
isConnectionStatusMinimized: boolean;
|
||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
|
shouldHideReadMarks?: boolean;
|
||||||
canTranslate: boolean;
|
canTranslate: boolean;
|
||||||
canTranslateChats: boolean;
|
canTranslateChats: boolean;
|
||||||
translationLanguage?: string;
|
translationLanguage?: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user