MiddleColumn: show similar channels after join channel (#4126)
This commit is contained in:
parent
c3c71cbc9e
commit
2bf38d0726
@ -2037,7 +2037,13 @@ export async function fetchChannelRecommendations({ chat }: { chat: ApiChat }) {
|
|||||||
|
|
||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
return result?.chats.map((_chat) => buildApiChatFromPreview(_chat)).filter(Boolean);
|
return {
|
||||||
|
similarChannels: result?.chats
|
||||||
|
.map((_chat) => buildApiChatFromPreview(_chat))
|
||||||
|
.filter(Boolean),
|
||||||
|
count:
|
||||||
|
result instanceof GramJs.messages.ChatsSlice ? result.count : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserPrivacyRestrictedUpdates(updates: GramJs.TypeUpdates) {
|
function handleUserPrivacyRestrictedUpdates(updates: GramJs.TypeUpdates) {
|
||||||
|
|||||||
@ -291,7 +291,14 @@ export interface ApiAction {
|
|||||||
text: string;
|
text: string;
|
||||||
targetUserIds?: string[];
|
targetUserIds?: string[];
|
||||||
targetChatId?: string;
|
targetChatId?: string;
|
||||||
type: 'historyClear' | 'contactSignUp' | 'chatCreate' | 'topicCreate' | 'suggestProfilePhoto' | 'other';
|
type:
|
||||||
|
| 'historyClear'
|
||||||
|
| 'contactSignUp'
|
||||||
|
| 'chatCreate'
|
||||||
|
| 'topicCreate'
|
||||||
|
| 'suggestProfilePhoto'
|
||||||
|
| 'joinedChannel'
|
||||||
|
| 'other';
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
amount?: number;
|
amount?: number;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import type { FocusDirection, ThreadId } from '../../types';
|
|||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getChatTitle, getMessageHtmlId, isChatChannel,
|
getChatTitle, getMessageHtmlId, isChatChannel, isJoinedChannelMessage,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
@ -42,6 +42,7 @@ import useFocusMessage from './message/hooks/useFocusMessage';
|
|||||||
import AnimatedIconFromSticker from '../common/AnimatedIconFromSticker';
|
import AnimatedIconFromSticker from '../common/AnimatedIconFromSticker';
|
||||||
import ActionMessageSuggestedAvatar from './ActionMessageSuggestedAvatar';
|
import ActionMessageSuggestedAvatar from './ActionMessageSuggestedAvatar';
|
||||||
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
import ContextMenuContainer from './message/ContextMenuContainer.async';
|
||||||
|
import SimilarChannels from './message/SimilarChannels';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
@ -129,6 +130,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const isGift = Boolean(message.content.action?.text.startsWith('ActionGift'));
|
const isGift = Boolean(message.content.action?.text.startsWith('ActionGift'));
|
||||||
const isGiftCode = Boolean(message.content.action?.text.startsWith('BoostingReceivedGift'));
|
const isGiftCode = Boolean(message.content.action?.text.startsWith('BoostingReceivedGift'));
|
||||||
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
||||||
|
const isJoinedMessage = isJoinedChannelMessage(message);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (noAppearanceAnimation) {
|
if (noAppearanceAnimation) {
|
||||||
@ -291,15 +293,15 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
{!isSuggestedAvatar && !isGiftCode && <span className="action-message-content">{renderContent()}</span>}
|
{!isSuggestedAvatar && !isGiftCode && !isJoinedMessage && (
|
||||||
|
<span className="action-message-content">{renderContent()}</span>
|
||||||
|
)}
|
||||||
{isGift && renderGift()}
|
{isGift && renderGift()}
|
||||||
{isGiftCode && renderGiftCode()}
|
{isGiftCode && renderGiftCode()}
|
||||||
{isSuggestedAvatar && (
|
{isSuggestedAvatar && (
|
||||||
<ActionMessageSuggestedAvatar
|
<ActionMessageSuggestedAvatar message={message} renderContent={renderContent} />
|
||||||
message={message}
|
|
||||||
renderContent={renderContent}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
{isJoinedMessage && <SimilarChannels chatId={targetChatId!} />}
|
||||||
{contextMenuPosition && (
|
{contextMenuPosition && (
|
||||||
<ContextMenuContainer
|
<ContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
|
|||||||
@ -219,6 +219,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.join-text,
|
||||||
.sticky-date,
|
.sticky-date,
|
||||||
.local-action-message,
|
.local-action-message,
|
||||||
.ActionMessage,
|
.ActionMessage,
|
||||||
|
|||||||
247
src/components/middle/message/SimilarChannels.module.scss
Normal file
247
src/components/middle/message/SimilarChannels.module.scss
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
.root {
|
||||||
|
max-width: 45rem;
|
||||||
|
margin-top: 0.625rem;
|
||||||
|
position: relative;
|
||||||
|
--more-channel-background: #d8d8d8;
|
||||||
|
--more-channel-background-dark: #8f8f8f;
|
||||||
|
--more-channel-badge: #8f8f8f;
|
||||||
|
--more-channel-badge-overlay: #00000033;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notch {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 0.1875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notch-path {
|
||||||
|
fill: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 0.375rem 0.375rem 0 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
padding: 0.125rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
> .icon {
|
||||||
|
margin-left: 0.0625rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
height: 8.5rem;
|
||||||
|
border-radius: 0.9375rem;
|
||||||
|
margin-top: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-appearing {
|
||||||
|
animation: 0.15s ease-out channels-appear forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-hiding {
|
||||||
|
animation: 0.15s ease-out channels-disappear forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes channels-appear {
|
||||||
|
from {
|
||||||
|
transform: scale(0) translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: none;
|
||||||
|
opacity: 1;
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
height: 8.9375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes channels-disappear {
|
||||||
|
from {
|
||||||
|
transform: none;
|
||||||
|
opacity: 1;
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
height: 8.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(0) translateY(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-list {
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-bottom-left-radius: 0.9375rem;
|
||||||
|
border-bottom-right-radius: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: 6.375rem;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
padding: 0.5rem 0.5rem 0.25rem 0.5rem;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-chat-hover);
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-item {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem 0 0.25rem 0;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 5rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 3.75rem;
|
||||||
|
height: 3.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-item .avatar {
|
||||||
|
z-index: 3;
|
||||||
|
outline: 0.125rem solid var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-item .badge {
|
||||||
|
z-index: 4;
|
||||||
|
align-self: center;
|
||||||
|
background: var(--more-channel-background);
|
||||||
|
|
||||||
|
:global(.theme-dark) & {
|
||||||
|
background: var(--more-channel-background-dark);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
max-width: 3.75rem;
|
||||||
|
height: 0.9375rem;
|
||||||
|
margin-top: -0.8125rem;
|
||||||
|
outline: 0.0625rem solid var(--color-background);
|
||||||
|
padding: 0.125rem 0.1875rem 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-white);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
background-color: var(--more-channel-badge-overlay);
|
||||||
|
position: absolute;
|
||||||
|
max-width: 3.75rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 0.9375rem;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 0.4375rem;
|
||||||
|
margin-right: 0.0625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.members-count {
|
||||||
|
font-size: 0.5625rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 0.6875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-title {
|
||||||
|
text-align: center;
|
||||||
|
height: 1.625rem;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 0.8125rem;
|
||||||
|
max-width: 3.4375rem;
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
white-space: normal;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-item .channel-title {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fake-avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 3.75rem;
|
||||||
|
height: 3.75rem;
|
||||||
|
position: absolute;
|
||||||
|
left: 0.625rem;
|
||||||
|
background: var(--more-channel-background);
|
||||||
|
outline: 0.125rem solid var(--color-background);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
:global(.theme-dark) & {
|
||||||
|
background: var(--more-channel-background-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fake-avatar-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-fake-avatar {
|
||||||
|
left: 1.25rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
266
src/components/middle/message/SimilarChannels.tsx
Normal file
266
src/components/middle/message/SimilarChannels.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import React, {
|
||||||
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiChat } from '../../../api/types';
|
||||||
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
|
|
||||||
|
import { getChatAvatarHash } from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectChat,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
|
selectSimilarChannelIds,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { getAverageColor, rgb2hex } from '../../../util/colors';
|
||||||
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
|
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
import useTimeout from '../../../hooks/useTimeout';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||||
|
|
||||||
|
import styles from './SimilarChannels.module.scss';
|
||||||
|
|
||||||
|
const DEFAULT_BADGE_COLOR = '#3C3C4399';
|
||||||
|
const SHOW_CHANNELS_NUMBER = 10;
|
||||||
|
const MIN_SKELETON_DELAY = 300;
|
||||||
|
const MAX_SKELETON_DELAY = 2000;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
chatId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
similarChannelIds?: string[];
|
||||||
|
shouldShowInChat?: boolean;
|
||||||
|
count: number;
|
||||||
|
isCurrentUserPremium: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SimilarChannels = ({
|
||||||
|
chatId,
|
||||||
|
similarChannelIds,
|
||||||
|
shouldShowInChat,
|
||||||
|
count,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
}: StateProps & OwnProps) => {
|
||||||
|
const lang = useLang();
|
||||||
|
const { toggleChannelRecommendations } = getActions();
|
||||||
|
const [isShowing, markShowing, markNotShowing] = useFlag(false);
|
||||||
|
const [isHiding, markHiding, markNotHiding] = useFlag(false);
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const similarChannels = useMemo(() => {
|
||||||
|
if (!similarChannelIds) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const global = getGlobal();
|
||||||
|
return similarChannelIds.map((id) => selectChat(global, id)).filter(Boolean);
|
||||||
|
}, [similarChannelIds]);
|
||||||
|
// Show skeleton while loading similar channels
|
||||||
|
const [shoulRenderSkeleton, setShoulRenderSkeleton] = useState(!similarChannelIds);
|
||||||
|
const firstSimilarChannels = useMemo(() => similarChannels?.slice(0, SHOW_CHANNELS_NUMBER), [similarChannels]);
|
||||||
|
const areSimilarChannelsPresent = Boolean(firstSimilarChannels?.length);
|
||||||
|
useHorizontalScroll(ref, !areSimilarChannelsPresent || !shouldShowInChat || shoulRenderSkeleton, true);
|
||||||
|
const isAnimating = isHiding || isShowing;
|
||||||
|
const shouldRenderChannels = Boolean(
|
||||||
|
!shoulRenderSkeleton
|
||||||
|
&& (shouldShowInChat || isAnimating)
|
||||||
|
&& areSimilarChannelsPresent,
|
||||||
|
);
|
||||||
|
|
||||||
|
useTimeout(() => setShoulRenderSkeleton(false), MAX_SKELETON_DELAY, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shoulRenderSkeleton && similarChannels && shouldShowInChat) {
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
setShoulRenderSkeleton(false);
|
||||||
|
}, MIN_SKELETON_DELAY);
|
||||||
|
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [similarChannels, shouldShowInChat, shoulRenderSkeleton]);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
toggleChannelRecommendations({ chatId });
|
||||||
|
if (shouldShowInChat) {
|
||||||
|
markNotShowing();
|
||||||
|
markHiding();
|
||||||
|
} else {
|
||||||
|
markShowing();
|
||||||
|
markNotHiding();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.root)}>
|
||||||
|
<div className="join-text">
|
||||||
|
<span
|
||||||
|
className={buildClassName(areSimilarChannelsPresent && styles.joinText)}
|
||||||
|
onClick={areSimilarChannelsPresent ? handleToggle : undefined}
|
||||||
|
>
|
||||||
|
{lang('ChannelJoined')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{shoulRenderSkeleton && <Skeleton className={styles.skeleton} />}
|
||||||
|
{shouldRenderChannels && (
|
||||||
|
<div
|
||||||
|
className={buildClassName(
|
||||||
|
isShowing && styles.isAppearing,
|
||||||
|
isHiding && styles.isHiding,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={styles.notch}>
|
||||||
|
<svg
|
||||||
|
width="19"
|
||||||
|
height="7"
|
||||||
|
viewBox="0 0 19 7"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
className={styles.notchPath}
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M19 7C16.8992 7 13.59 3.88897 11.5003 1.67424C10.7648 0.894688 10.397 0.50491 10.0434 0.385149C9.70568 0.270811 9.4225 0.270474 9.08456 0.38401C8.73059 0.50293 8.36133 0.892443 7.62279 1.67147C5.52303 3.88637 2.18302 7 0 7L19 7Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inner}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<span className={styles.title}>{lang('SimilarChannels')}</span>
|
||||||
|
<Button
|
||||||
|
className={styles.close}
|
||||||
|
color="translucent"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={handleToggle}
|
||||||
|
>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div ref={ref} className={buildClassName(styles.channelList, 'no-scrollbar')}>
|
||||||
|
{firstSimilarChannels?.map((channel, i) => {
|
||||||
|
return i === SHOW_CHANNELS_NUMBER - 1 ? (
|
||||||
|
<MoreChannels
|
||||||
|
channel={channel}
|
||||||
|
chatId={chatId}
|
||||||
|
channelsCount={count - SHOW_CHANNELS_NUMBER + 1}
|
||||||
|
isCurrentUserPremium={isCurrentUserPremium}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SimilarChannel channel={channel} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function SimilarChannel({ channel }: { channel: ApiChat }) {
|
||||||
|
const { openChat } = getActions();
|
||||||
|
const color = useAverageColor(channel);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.item} onClick={() => openChat({ id: channel.id })}>
|
||||||
|
<Avatar className={styles.avatar} key={channel.id} size="large" peer={channel} />
|
||||||
|
<div style={`background: ${color}`} className={styles.badge}>
|
||||||
|
<i className={buildClassName(styles.icon, 'icon icon-user-filled')} />
|
||||||
|
<span className={styles.membersCount}>{formatIntegerCompact(channel?.membersCount || 0)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className={styles.channelTitle}>{channel.title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MoreChannels({
|
||||||
|
channel,
|
||||||
|
chatId,
|
||||||
|
channelsCount,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
}: {
|
||||||
|
channel: ApiChat;
|
||||||
|
chatId: string;
|
||||||
|
channelsCount: number;
|
||||||
|
isCurrentUserPremium: boolean;
|
||||||
|
}) {
|
||||||
|
const { openPremiumModal, openChatWithInfo } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const handleClickMore = () => {
|
||||||
|
if (isCurrentUserPremium) {
|
||||||
|
openChatWithInfo({
|
||||||
|
id: chatId, shouldReplaceHistory: true, profileTab: 'similarChannels', forceScrollProfileTab: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openPremiumModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={buildClassName(styles.item, styles.lastItem)}
|
||||||
|
onClick={() => handleClickMore()}
|
||||||
|
>
|
||||||
|
<Avatar className={styles.avatar} key={channel.id} size="large" peer={channel} />
|
||||||
|
<div className={styles.fakeAvatar}>
|
||||||
|
<div className={styles.fakeAvatarInner} />
|
||||||
|
</div>
|
||||||
|
<div className={buildClassName(styles.fakeAvatar, styles.lastFakeAvatar)}>
|
||||||
|
<div className={styles.fakeAvatarInner} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.badge}>
|
||||||
|
<span className={styles.membersCount}>{`+${channelsCount}`}</span>
|
||||||
|
{!isCurrentUserPremium && <Icon name="lock-badge" className={styles.icon} />}
|
||||||
|
</div>
|
||||||
|
<span className={styles.channelTitle}>{lang('MoreSimilar')}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function useAverageColor(channel: ApiChat) {
|
||||||
|
const [color, setColor] = useState(DEFAULT_BADGE_COLOR);
|
||||||
|
const imgBlobUrl = useMedia(getChatAvatarHash(channel), false, ApiMediaFormat.BlobUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (!imgBlobUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageColor = await getAverageColor(imgBlobUrl);
|
||||||
|
setColor(`#${rgb2hex(averageColor)}`);
|
||||||
|
})();
|
||||||
|
}, [imgBlobUrl]);
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(
|
||||||
|
withGlobal<OwnProps>((global, { chatId }): StateProps => {
|
||||||
|
const { similarChannelIds, shouldShowInChat, count } = selectSimilarChannelIds(global, chatId) || {};
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
|
return {
|
||||||
|
similarChannelIds,
|
||||||
|
shouldShowInChat,
|
||||||
|
count,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
};
|
||||||
|
})(SimilarChannels),
|
||||||
|
);
|
||||||
@ -125,6 +125,7 @@ type StateProps = {
|
|||||||
limitSimilarChannels: number;
|
limitSimilarChannels: number;
|
||||||
isTopicInfo?: boolean;
|
isTopicInfo?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
|
forceScrollProfileTab?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TabProps = {
|
type TabProps = {
|
||||||
@ -180,6 +181,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
limitSimilarChannels,
|
limitSimilarChannels,
|
||||||
isTopicInfo,
|
isTopicInfo,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
|
forceScrollProfileTab,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setLocalMediaSearchType,
|
setLocalMediaSearchType,
|
||||||
@ -254,10 +256,10 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
}, [nextProfileTab, tabs]);
|
}, [nextProfileTab, tabs]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isChannel) {
|
if (isChannel && !similarChannels) {
|
||||||
fetchChannelRecommendations({ chatId });
|
fetchChannelRecommendations({ chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, isChannel]);
|
}, [chatId, isChannel, similarChannels]);
|
||||||
|
|
||||||
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
||||||
const tabType = tabs[renderingActiveTab].type as ProfileTabType;
|
const tabType = tabs[renderingActiveTab].type as ProfileTabType;
|
||||||
@ -296,7 +298,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
usePeerStoriesPolling(resultType === 'members' ? viewportIds as string[] : undefined);
|
usePeerStoriesPolling(resultType === 'members' ? viewportIds as string[] : undefined);
|
||||||
|
|
||||||
const { handleScroll } = useProfileState(containerRef, resultType, profileState, onProfileStateChange);
|
const { handleScroll } = useProfileState(
|
||||||
|
containerRef,
|
||||||
|
resultType,
|
||||||
|
profileState,
|
||||||
|
onProfileStateChange,
|
||||||
|
forceScrollProfileTab,
|
||||||
|
);
|
||||||
|
|
||||||
const { applyTransitionFix, releaseTransitionFix } = useTransitionFixes(containerRef);
|
const { applyTransitionFix, releaseTransitionFix } = useTransitionFixes(containerRef);
|
||||||
|
|
||||||
@ -693,7 +701,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator);
|
&& (getHasAdminRight(chat, 'inviteUsers') || !isUserRightBanned(chat, 'inviteUsers') || chat.isCreator);
|
||||||
const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator);
|
const canDeleteMembers = hasMembersTab && chat && (getHasAdminRight(chat, 'banUsers') || chat.isCreator);
|
||||||
const activeDownloads = selectActiveDownloads(global, chatId);
|
const activeDownloads = selectActiveDownloads(global, chatId);
|
||||||
const similarChannels = selectSimilarChannelIds(global, chatId);
|
const { similarChannelIds } = selectSimilarChannelIds(global, chatId) || {};
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
let hasCommonChatsTab;
|
let hasCommonChatsTab;
|
||||||
@ -739,8 +747,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
storyByIds,
|
storyByIds,
|
||||||
isChatProtected: chat?.isProtected,
|
isChatProtected: chat?.isProtected,
|
||||||
nextProfileTab: selectTabState(global).nextProfileTab,
|
nextProfileTab: selectTabState(global).nextProfileTab,
|
||||||
|
forceScrollProfileTab: selectTabState(global).forceScrollProfileTab,
|
||||||
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
||||||
similarChannels,
|
similarChannels: similarChannelIds,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
isTopicInfo,
|
isTopicInfo,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
|
|||||||
@ -20,10 +20,11 @@ export default function useProfileState(
|
|||||||
tabType: ProfileTabType,
|
tabType: ProfileTabType,
|
||||||
profileState: ProfileState,
|
profileState: ProfileState,
|
||||||
onProfileStateChange: (state: ProfileState) => void,
|
onProfileStateChange: (state: ProfileState) => void,
|
||||||
|
forceScrollProfileTab = false,
|
||||||
) {
|
) {
|
||||||
// Scroll to tabs if needed
|
// Scroll to tabs if needed
|
||||||
useEffectWithPrevDeps(([prevTabType]) => {
|
useEffectWithPrevDeps(([prevTabType]) => {
|
||||||
if (prevTabType && prevTabType !== tabType) {
|
if ((prevTabType && prevTabType !== tabType) || (tabType && forceScrollProfileTab)) {
|
||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
const tabsEl = container.querySelector<HTMLDivElement>('.TabList')!;
|
const tabsEl = container.querySelector<HTMLDivElement>('.TabList')!;
|
||||||
if (container.scrollTop < tabsEl.offsetTop) {
|
if (container.scrollTop < tabsEl.offsetTop) {
|
||||||
@ -35,7 +36,7 @@ export default function useProfileState(
|
|||||||
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [tabType, onProfileStateChange, containerRef]);
|
}, [tabType, onProfileStateChange, containerRef, forceScrollProfileTab]);
|
||||||
|
|
||||||
// Scroll to top
|
// Scroll to top
|
||||||
useEffectWithPrevDeps(([prevProfileState]) => {
|
useEffectWithPrevDeps(([prevProfileState]) => {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import {
|
|||||||
isChatBasicGroup,
|
isChatBasicGroup,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
|
isLocalMessageId,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
toChannelId,
|
toChannelId,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
@ -58,6 +59,7 @@ import {
|
|||||||
addUsers,
|
addUsers,
|
||||||
addUserStatuses,
|
addUserStatuses,
|
||||||
addUsersToRestrictedInviteList,
|
addUsersToRestrictedInviteList,
|
||||||
|
deleteChatMessages,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
removeChatFromChatLists,
|
removeChatFromChatLists,
|
||||||
@ -67,6 +69,7 @@ import {
|
|||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
replaceUsers,
|
replaceUsers,
|
||||||
replaceUserStatuses,
|
replaceUserStatuses,
|
||||||
|
toggleSimilarChannels,
|
||||||
updateChat,
|
updateChat,
|
||||||
updateChatFullInfo,
|
updateChatFullInfo,
|
||||||
updateChatLastMessageId,
|
updateChatLastMessageId,
|
||||||
@ -93,6 +96,7 @@ import {
|
|||||||
selectChatLastMessage,
|
selectChatLastMessage,
|
||||||
selectChatLastMessageId,
|
selectChatLastMessageId,
|
||||||
selectChatListType,
|
selectChatListType,
|
||||||
|
selectChatMessages,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
@ -798,7 +802,7 @@ addActionHandler('deleteChat', (global, actions, payload): ActionReturnType => {
|
|||||||
void callApi('deleteChat', { chatId: chat.id });
|
void callApi('deleteChat', { chatId: chat.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('leaveChannel', (global, actions, payload): ActionReturnType => {
|
addActionHandler('leaveChannel', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, tabId = getCurrentTabId() } = payload!;
|
const { chatId, tabId = getCurrentTabId() } = payload!;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
@ -814,7 +818,12 @@ addActionHandler('leaveChannel', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
const { id: channelId, accessHash } = chat;
|
const { id: channelId, accessHash } = chat;
|
||||||
if (channelId && accessHash) {
|
if (channelId && accessHash) {
|
||||||
void callApi('leaveChannel', { channelId, accessHash });
|
await callApi('leaveChannel', { channelId, accessHash });
|
||||||
|
global = getGlobal();
|
||||||
|
const chatMessages = selectChatMessages(global, chatId);
|
||||||
|
const localMessageIds = Object.keys(chatMessages).map(Number).filter(isLocalMessageId);
|
||||||
|
global = deleteChatMessages(global, chatId, localMessageIds);
|
||||||
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2615,9 +2624,9 @@ addActionHandler('fetchChannelRecommendations', async (global, actions, payload)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const similarChannels = await callApi('fetchChannelRecommendations', {
|
const { similarChannels, count } = await callApi('fetchChannelRecommendations', {
|
||||||
chat,
|
chat,
|
||||||
});
|
}) || {};
|
||||||
|
|
||||||
if (!similarChannels) {
|
if (!similarChannels) {
|
||||||
return;
|
return;
|
||||||
@ -2625,8 +2634,19 @@ addActionHandler('fetchChannelRecommendations', async (global, actions, payload)
|
|||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addChats(global, buildCollectionByKey(similarChannels, 'id'));
|
global = addChats(global, buildCollectionByKey(similarChannels, 'id'));
|
||||||
global = addSimilarChannels(global, chatId, similarChannels.map((channel) => channel.id));
|
global = addSimilarChannels(global, chatId, similarChannels.map((channel) => channel.id), count);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleChannelRecommendations', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { chatId } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = toggleSimilarChannels(global, chatId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,13 @@ import { MAIN_THREAD_ID } from '../../../api/types';
|
|||||||
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
||||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||||
import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications';
|
import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications';
|
||||||
|
import { buildLocalMessage } from '../../../api/gramjs/apiBuilders/messages';
|
||||||
|
import { isChatChannel, isLocalMessageId } from '../../helpers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import {
|
import {
|
||||||
|
deleteChatMessages,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
updateChat,
|
updateChat,
|
||||||
@ -23,7 +26,9 @@ import { updateTabState } from '../../reducers/tabs';
|
|||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatListType,
|
selectChatListType,
|
||||||
|
selectChatMessages,
|
||||||
selectCommonBoxChatId,
|
selectCommonBoxChatId,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsChatListed,
|
selectIsChatListed,
|
||||||
@ -87,6 +92,26 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
case 'updateChatJoin': {
|
case 'updateChatJoin': {
|
||||||
const listType = selectChatListType(global, update.id);
|
const listType = selectChatListType(global, update.id);
|
||||||
|
const chat = selectChat(global, update.id);
|
||||||
|
if (chat && isChatChannel(chat)) {
|
||||||
|
actions.fetchChannelRecommendations({ chatId: chat.id });
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, chat.id);
|
||||||
|
const localMessage = buildLocalMessage(chat, lastMessageId);
|
||||||
|
localMessage.content.action = {
|
||||||
|
text: 'you joined this channel',
|
||||||
|
translationValues: ['ChannelJoined'],
|
||||||
|
type: 'joinedChannel',
|
||||||
|
targetChatId: chat.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
actions.apiUpdate({
|
||||||
|
'@type': 'newMessage',
|
||||||
|
id: localMessage.id,
|
||||||
|
chatId: chat.id,
|
||||||
|
message: localMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!listType) {
|
if (!listType) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -95,7 +120,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
global = updateChat(global, update.id, { isNotJoined: false });
|
global = updateChat(global, update.id, { isNotJoined: false });
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const chat = selectChat(global, update.id);
|
|
||||||
if (chat) {
|
if (chat) {
|
||||||
actions.requestChatUpdate({ chatId: chat.id });
|
actions.requestChatUpdate({ chatId: chat.id });
|
||||||
}
|
}
|
||||||
@ -104,7 +128,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'updateChatLeave': {
|
case 'updateChatLeave': {
|
||||||
return leaveChat(global, update.id);
|
global = leaveChat(global, update.id);
|
||||||
|
const chat = selectChat(global, update.id);
|
||||||
|
if (chat && isChatChannel(chat)) {
|
||||||
|
const chatMessages = selectChatMessages(global, update.id);
|
||||||
|
const localMessageIds = Object.keys(chatMessages).map(Number).filter(isLocalMessageId);
|
||||||
|
global = deleteChatMessages(global, chat.id, localMessageIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateChatInbox': {
|
case 'updateChatInbox': {
|
||||||
|
|||||||
@ -97,12 +97,13 @@ addActionHandler('openPreviousChat', (global, actions, payload): ActionReturnTyp
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnType => {
|
||||||
const { profileTab, tabId = getCurrentTabId() } = payload;
|
const { profileTab, forceScrollProfileTab = false, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
...selectTabState(global, tabId),
|
...selectTabState(global, tabId),
|
||||||
isChatInfoShown: true,
|
isChatInfoShown: true,
|
||||||
nextProfileTab: profileTab,
|
nextProfileTab: profileTab,
|
||||||
|
forceScrollProfileTab,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
global = { ...global, lastIsChatInfoShown: true };
|
global = { ...global, lastIsChatInfoShown: true };
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { getServerTime } from '../../../util/serverTime';
|
|||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
import versionNotification from '../../../versionNotification.txt';
|
import versionNotification from '../../../versionNotification.txt';
|
||||||
import {
|
import {
|
||||||
getIsSavedDialog, getMessageSummaryText, getSenderTitle, isChatChannel,
|
getIsSavedDialog, getMessageSummaryText, getSenderTitle, isChatChannel, isJoinedChannelMessage,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
@ -330,6 +330,13 @@ addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnTyp
|
|||||||
lastMessageId = pinnedMessageIds[pinnedMessageIds.length - 1];
|
lastMessageId = pinnedMessageIds[pinnedMessageIds.length - 1];
|
||||||
} else {
|
} else {
|
||||||
lastMessageId = selectChatLastMessageId(global, chatId);
|
lastMessageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
|
const chatMessages = selectChatMessages(global, chatId);
|
||||||
|
// Workaround for scroll to local message 'you joined this channel'
|
||||||
|
const lastChatMessage = Object.values(chatMessages).reverse()[0];
|
||||||
|
if (lastMessageId && isJoinedChannelMessage(lastChatMessage) && lastChatMessage.id > lastMessageId) {
|
||||||
|
lastMessageId = lastChatMessage.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (isSavedDialog) {
|
} else if (isSavedDialog) {
|
||||||
lastMessageId = selectChatLastMessageId(global, String(threadId), 'saved');
|
lastMessageId = selectChatLastMessageId(global, String(threadId), 'saved');
|
||||||
|
|||||||
@ -151,7 +151,7 @@ addActionHandler('resetNextProfileTab', (global, actions, payload): ActionReturn
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateTabState(global, { nextProfileTab: undefined }, tabId);
|
return updateTabState(global, { nextProfileTab: undefined, forceScrollProfileTab: false }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('toggleStatistics', (global, actions, payload): ActionReturnType => {
|
addActionHandler('toggleStatistics', (global, actions, payload): ActionReturnType => {
|
||||||
|
|||||||
@ -354,3 +354,7 @@ export function isExpiredMessage(message: ApiMessage) {
|
|||||||
export function hasMessageTtl(message: ApiMessage) {
|
export function hasMessageTtl(message: ApiMessage) {
|
||||||
return message.content?.ttlSeconds !== undefined;
|
return message.content?.ttlSeconds !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isJoinedChannelMessage(message: ApiMessage) {
|
||||||
|
return message.content.action && message.content.action.type === 'joinedChannel';
|
||||||
|
}
|
||||||
|
|||||||
@ -446,7 +446,9 @@ export function deleteTopic<T extends GlobalState>(
|
|||||||
export function addSimilarChannels<T extends GlobalState>(
|
export function addSimilarChannels<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
similarChannels: string[],
|
similarChannelIds: string[],
|
||||||
|
count?: number,
|
||||||
|
shouldShowInChat = true,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
@ -454,7 +456,33 @@ export function addSimilarChannels<T extends GlobalState>(
|
|||||||
...global.chats,
|
...global.chats,
|
||||||
similarChannelsById: {
|
similarChannelsById: {
|
||||||
...global.chats.similarChannelsById,
|
...global.chats.similarChannelsById,
|
||||||
[chatId]: similarChannels,
|
[chatId]: {
|
||||||
|
similarChannelIds,
|
||||||
|
count: count || similarChannelIds.length,
|
||||||
|
shouldShowInChat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleSimilarChannels<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
chatId: string,
|
||||||
|
) {
|
||||||
|
const similarChannels = global.chats.similarChannelsById[chatId];
|
||||||
|
const shouldShowInChat = !global.chats.similarChannelsById[chatId].shouldShowInChat;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
similarChannelsById: {
|
||||||
|
...global.chats.similarChannelsById,
|
||||||
|
[chatId]: {
|
||||||
|
...similarChannels,
|
||||||
|
shouldShowInChat,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -325,7 +325,7 @@ export function selectRequestedChatTranslationLanguage<T extends GlobalState>(
|
|||||||
export function selectSimilarChannelIds<T extends GlobalState>(
|
export function selectSimilarChannelIds<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
): string[] | undefined {
|
) {
|
||||||
return global.chats.similarChannelsById[chatId];
|
return global.chats.similarChannelsById[chatId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -234,6 +234,7 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
nextProfileTab?: ProfileTabType;
|
nextProfileTab?: ProfileTabType;
|
||||||
|
forceScrollProfileTab?: boolean;
|
||||||
nextSettingsScreen?: SettingsScreens;
|
nextSettingsScreen?: SettingsScreens;
|
||||||
nextFoldersAction?: ReducerAction<FoldersActions>;
|
nextFoldersAction?: ReducerAction<FoldersActions>;
|
||||||
shareFolderScreen?: {
|
shareFolderScreen?: {
|
||||||
@ -807,7 +808,14 @@ export type GlobalState = {
|
|||||||
forDiscussionIds?: string[];
|
forDiscussionIds?: string[];
|
||||||
// Obtained from GetFullChat / GetFullChannel
|
// Obtained from GetFullChat / GetFullChannel
|
||||||
fullInfoById: Record<string, ApiChatFullInfo>;
|
fullInfoById: Record<string, ApiChatFullInfo>;
|
||||||
similarChannelsById: Record<string, string[]>;
|
similarChannelsById: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
shouldShowInChat: boolean;
|
||||||
|
similarChannelIds: string[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
messages: {
|
messages: {
|
||||||
@ -1223,7 +1231,10 @@ export interface ActionPayloads {
|
|||||||
onReplace?: VoidFunction;
|
onReplace?: VoidFunction;
|
||||||
shouldReplace?: boolean;
|
shouldReplace?: boolean;
|
||||||
};
|
};
|
||||||
openChatWithInfo: ActionPayloads['openChat'] & { profileTab?: ProfileTabType } & WithTabId;
|
openChatWithInfo: ActionPayloads['openChat'] & {
|
||||||
|
profileTab?: ProfileTabType;
|
||||||
|
forceScrollProfileTab?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
openThreadWithInfo: ActionPayloads['openThread'] & WithTabId;
|
openThreadWithInfo: ActionPayloads['openThread'] & WithTabId;
|
||||||
openLinkedChat: { id: string } & WithTabId;
|
openLinkedChat: { id: string } & WithTabId;
|
||||||
loadMoreMembers: WithTabId | undefined;
|
loadMoreMembers: WithTabId | undefined;
|
||||||
@ -1809,6 +1820,9 @@ export interface ActionPayloads {
|
|||||||
fetchChannelRecommendations: {
|
fetchChannelRecommendations: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
toggleChannelRecommendations: {
|
||||||
|
chatId: string;
|
||||||
|
};
|
||||||
updateChatMutedState: {
|
updateChatMutedState: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user