Folders: Fix sharing (#3142)
This commit is contained in:
parent
d04177aea8
commit
4b96cc5bfe
@ -1681,7 +1681,7 @@ export async function editChatlistInvite({
|
|||||||
slug,
|
slug,
|
||||||
title,
|
title,
|
||||||
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
|
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
|
||||||
}));
|
}), undefined, true);
|
||||||
if (!result) return undefined;
|
if (!result) return undefined;
|
||||||
|
|
||||||
return buildApiChatlistExportedInvite(result);
|
return buildApiChatlistExportedInvite(result);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding-right: 3rem;
|
padding-right: 3rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.moreMenu {
|
.moreMenu {
|
||||||
|
|||||||
@ -18,12 +18,14 @@ type OwnProps = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
inviteLink: string;
|
inviteLink: string;
|
||||||
onRevoke?: VoidFunction;
|
onRevoke?: VoidFunction;
|
||||||
|
isDisabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InviteLink: FC<OwnProps> = ({
|
const InviteLink: FC<OwnProps> = ({
|
||||||
title,
|
title,
|
||||||
inviteLink,
|
inviteLink,
|
||||||
onRevoke,
|
onRevoke,
|
||||||
|
isDisabled,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const { showNotification, openChatWithDraft } = getActions();
|
const { showNotification, openChatWithDraft } = getActions();
|
||||||
@ -85,10 +87,20 @@ const InviteLink: FC<OwnProps> = ({
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Button onClick={handleCopyPrimaryClicked} className={styles.button}>
|
<Button
|
||||||
|
onClick={handleCopyPrimaryClicked}
|
||||||
|
className={styles.button}
|
||||||
|
size="smaller"
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
{lang('FolderLinkScreen.LinkActionCopy')}
|
{lang('FolderLinkScreen.LinkActionCopy')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleShare} className={styles.button}>
|
<Button
|
||||||
|
onClick={handleShare}
|
||||||
|
className={styles.button}
|
||||||
|
size="smaller"
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
{lang('FolderLinkScreen.LinkActionShare')}
|
{lang('FolderLinkScreen.LinkActionShare')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { isUserId } from '../../global/helpers';
|
import { isUserId } from '../../global/helpers';
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -32,6 +33,7 @@ type OwnProps = {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
noScrollRestore?: boolean;
|
noScrollRestore?: boolean;
|
||||||
isSearchable?: boolean;
|
isSearchable?: boolean;
|
||||||
|
isRoundCheckbox?: boolean;
|
||||||
lockedIds?: string[];
|
lockedIds?: string[];
|
||||||
onSelectedIdsChange?: (ids: string[]) => void;
|
onSelectedIdsChange?: (ids: string[]) => void;
|
||||||
onFilterChange?: (value: string) => void;
|
onFilterChange?: (value: string) => void;
|
||||||
@ -55,6 +57,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
isLoading,
|
isLoading,
|
||||||
noScrollRestore,
|
noScrollRestore,
|
||||||
isSearchable,
|
isSearchable,
|
||||||
|
isRoundCheckbox,
|
||||||
lockedIds,
|
lockedIds,
|
||||||
onSelectedIdsChange,
|
onSelectedIdsChange,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
@ -161,24 +164,37 @@ const Picker: FC<OwnProps> = ({
|
|||||||
onLoadMore={getMore}
|
onLoadMore={getMore}
|
||||||
noScrollRestore={noScrollRestore}
|
noScrollRestore={noScrollRestore}
|
||||||
>
|
>
|
||||||
{viewportIds.map((id) => (
|
{viewportIds.map((id) => {
|
||||||
<ListItem
|
const renderCheckbox = () => {
|
||||||
key={id}
|
return (
|
||||||
className="chat-item-clickable picker-list-item"
|
<Checkbox
|
||||||
disabled={lockedIdsSet.has(id)}
|
label=""
|
||||||
allowDisabledClick={Boolean(onDisabledClick)}
|
disabled={lockedIdsSet.has(id)}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
checked={selectedIds.includes(id)}
|
||||||
onClick={() => handleItemClick(id)}
|
round={isRoundCheckbox}
|
||||||
ripple
|
/>
|
||||||
>
|
);
|
||||||
<Checkbox label="" disabled={lockedIdsSet.has(id)} checked={selectedIds.includes(id)} />
|
};
|
||||||
{isUserId(id) ? (
|
return (
|
||||||
<PrivateChatInfo userId={id} />
|
<ListItem
|
||||||
) : (
|
key={id}
|
||||||
<GroupChatInfo chatId={id} />
|
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
||||||
)}
|
disabled={lockedIdsSet.has(id)}
|
||||||
</ListItem>
|
allowDisabledClick={Boolean(onDisabledClick)}
|
||||||
))}
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => handleItemClick(id)}
|
||||||
|
ripple
|
||||||
|
>
|
||||||
|
{!isRoundCheckbox ? renderCheckbox() : undefined}
|
||||||
|
{isUserId(id) ? (
|
||||||
|
<PrivateChatInfo userId={id} />
|
||||||
|
) : (
|
||||||
|
<GroupChatInfo chatId={id} />
|
||||||
|
)}
|
||||||
|
{isRoundCheckbox ? renderCheckbox() : undefined}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
) : !isLoading && viewportIds && !viewportIds.length ? (
|
) : !isLoading && viewportIds && !viewportIds.length ? (
|
||||||
<p className="no-results">{notFoundText || 'Sorry, nothing found.'}</p>
|
<p className="no-results">{notFoundText || 'Sorry, nothing found.'}</p>
|
||||||
|
|||||||
@ -6,8 +6,10 @@ import type { ApiChatFolder } from '../../../../api/types';
|
|||||||
import { SettingsScreens } from '../../../../types';
|
import { SettingsScreens } from '../../../../types';
|
||||||
import type { FolderEditDispatch, FoldersState } from '../../../../hooks/reducers/useFoldersReducer';
|
import type { FolderEditDispatch, FoldersState } from '../../../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
|
import { selectChatFilters } from '../../../../hooks/reducers/useFoldersReducer';
|
||||||
|
|
||||||
import SettingsFoldersMain from './SettingsFoldersMain';
|
import SettingsFoldersMain from './SettingsFoldersMain';
|
||||||
import SettingsFoldersEdit from './SettingsFoldersEdit';
|
import SettingsFoldersEdit, { ERROR_NO_CHATS, ERROR_NO_TITLE } from './SettingsFoldersEdit';
|
||||||
import SettingsFoldersChatFilters from './SettingsFoldersChatFilters';
|
import SettingsFoldersChatFilters from './SettingsFoldersChatFilters';
|
||||||
import SettingsShareChatlist from './SettingsShareChatlist';
|
import SettingsShareChatlist from './SettingsShareChatlist';
|
||||||
|
|
||||||
@ -34,7 +36,11 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
const { openShareChatFolderModal } = getActions();
|
const {
|
||||||
|
openShareChatFolderModal,
|
||||||
|
editChatFolder,
|
||||||
|
addChatFolder,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
@ -66,10 +72,50 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
currentScreen, onReset, onScreenSelect,
|
currentScreen, onReset, onScreenSelect,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isCreating = state.mode === 'create';
|
||||||
|
|
||||||
|
const saveState = useCallback((newState: FoldersState) => {
|
||||||
|
const { title } = newState.folder;
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
dispatch({ type: 'setError', payload: ERROR_NO_TITLE });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedChatIds: includedChatIds,
|
||||||
|
selectedChatTypes: includedChatTypes,
|
||||||
|
} = selectChatFilters(newState, 'included');
|
||||||
|
|
||||||
|
if (!includedChatIds.length && !Object.keys(includedChatTypes).length) {
|
||||||
|
dispatch({ type: 'setError', payload: ERROR_NO_CHATS });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCreating) {
|
||||||
|
editChatFolder({ id: newState.folderId!, folderUpdate: newState.folder });
|
||||||
|
} else {
|
||||||
|
addChatFolder({ folder: newState.folder as ApiChatFolder });
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: 'setError', payload: undefined });
|
||||||
|
dispatch({ type: 'setIsTouched', payload: false });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [dispatch, isCreating]);
|
||||||
|
|
||||||
|
const handleSaveFolder = useCallback((cb?: NoneToVoidFunction) => {
|
||||||
|
if (!saveState(state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb?.();
|
||||||
|
}, [saveState, state]);
|
||||||
|
|
||||||
const handleSaveFilter = useCallback(() => {
|
const handleSaveFilter = useCallback(() => {
|
||||||
dispatch({ type: 'saveFilters' });
|
const newState = dispatch({ type: 'saveFilters' });
|
||||||
handleReset();
|
handleReset();
|
||||||
}, [dispatch, handleReset]);
|
saveState(newState);
|
||||||
|
}, [dispatch, handleReset, saveState]);
|
||||||
|
|
||||||
const handleCreateFolder = useCallback(() => {
|
const handleCreateFolder = useCallback(() => {
|
||||||
dispatch({ type: 'reset' });
|
dispatch({ type: 'reset' });
|
||||||
@ -97,8 +143,9 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const handleShareFolder = useCallback(() => {
|
const handleShareFolder = useCallback(() => {
|
||||||
openShareChatFolderModal({ folderId: state.folderId!, noRequestNextScreen: true });
|
openShareChatFolderModal({ folderId: state.folderId!, noRequestNextScreen: true });
|
||||||
|
dispatch({ type: 'setIsChatlist', payload: true });
|
||||||
onScreenSelect(SettingsScreens.FoldersShare);
|
onScreenSelect(SettingsScreens.FoldersShare);
|
||||||
}, [onScreenSelect, state.folderId]);
|
}, [dispatch, onScreenSelect, state.folderId]);
|
||||||
|
|
||||||
const handleOpenInvite = useCallback((url: string) => {
|
const handleOpenInvite = useCallback((url: string) => {
|
||||||
openShareChatFolderModal({ folderId: state.folderId!, url, noRequestNextScreen: true });
|
openShareChatFolderModal({ folderId: state.folderId!, url, noRequestNextScreen: true });
|
||||||
@ -139,6 +186,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
|||||||
].includes(shownScreen)}
|
].includes(shownScreen)}
|
||||||
isOnlyInvites={currentScreen === SettingsScreens.FoldersEditFolderInvites}
|
isOnlyInvites={currentScreen === SettingsScreens.FoldersEditFolderInvites}
|
||||||
onBack={onReset}
|
onBack={onReset}
|
||||||
|
onSaveFolder={handleSaveFolder}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case SettingsScreens.FoldersIncludedChats:
|
case SettingsScreens.FoldersIncludedChats:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiChatFolder, ApiChatlistExportedInvite } from '../../../../api/types';
|
import type { ApiChatlistExportedInvite } from '../../../../api/types';
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
@ -45,6 +45,7 @@ type OwnProps = {
|
|||||||
isOnlyInvites?: boolean;
|
isOnlyInvites?: boolean;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
onSaveFolder: (cb?: VoidFunction) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -59,8 +60,8 @@ const SUBMIT_TIMEOUT = 500;
|
|||||||
|
|
||||||
const INITIAL_CHATS_LIMIT = 5;
|
const INITIAL_CHATS_LIMIT = 5;
|
||||||
|
|
||||||
const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
export const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
||||||
const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
export const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
||||||
|
|
||||||
const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||||
state,
|
state,
|
||||||
@ -78,10 +79,9 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
loadedArchivedChatIds,
|
loadedArchivedChatIds,
|
||||||
invites,
|
invites,
|
||||||
maxInviteLinks,
|
maxInviteLinks,
|
||||||
|
onSaveFolder,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
editChatFolder,
|
|
||||||
addChatFolder,
|
|
||||||
loadChatlistInvites,
|
loadChatlistInvites,
|
||||||
openLimitReachedModal,
|
openLimitReachedModal,
|
||||||
showNotification,
|
showNotification,
|
||||||
@ -154,38 +154,23 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
dispatch({ type: 'setTitle', payload: currentTarget.value.trim() });
|
dispatch({ type: 'setTitle', payload: currentTarget.value.trim() });
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleSaveFolder = useCallback((cb?: NoneToVoidFunction) => {
|
|
||||||
const { title } = state.folder;
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
dispatch({ type: 'setError', payload: ERROR_NO_TITLE });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!includedChatIds.length && !Object.keys(includedChatTypes).length) {
|
|
||||||
dispatch({ type: 'setError', payload: ERROR_NO_CHATS });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCreating) {
|
|
||||||
editChatFolder({ id: state.folderId!, folderUpdate: state.folder });
|
|
||||||
} else {
|
|
||||||
addChatFolder({ folder: state.folder as ApiChatFolder });
|
|
||||||
}
|
|
||||||
cb?.();
|
|
||||||
}, [dispatch, includedChatIds.length, includedChatTypes, isCreating, state.folder, state.folderId]);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
handleSaveFolder();
|
|
||||||
|
|
||||||
dispatch({ type: 'setIsLoading', payload: true });
|
dispatch({ type: 'setIsLoading', payload: true });
|
||||||
setTimeout(() => {
|
|
||||||
onReset();
|
onSaveFolder(() => {
|
||||||
}, SUBMIT_TIMEOUT);
|
setTimeout(() => {
|
||||||
}, [dispatch, handleSaveFolder, onReset]);
|
onReset();
|
||||||
|
}, SUBMIT_TIMEOUT);
|
||||||
|
});
|
||||||
|
}, [dispatch, onSaveFolder, onReset]);
|
||||||
|
|
||||||
const handleCreateInviteClick = useCallback(() => {
|
const handleCreateInviteClick = useCallback(() => {
|
||||||
if (!invites) return;
|
if (!invites) {
|
||||||
|
if (isCreating) {
|
||||||
|
onSaveFolder(onShareFolder);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Ignoring global updates is a known drawback here
|
// Ignoring global updates is a known drawback here
|
||||||
if (!selectCanShareFolder(getGlobal(), state.folderId!)) {
|
if (!selectCanShareFolder(getGlobal(), state.folderId!)) {
|
||||||
@ -195,7 +180,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (invites.length < maxInviteLinks) {
|
if (invites.length < maxInviteLinks) {
|
||||||
if (state.isTouched) {
|
if (state.isTouched) {
|
||||||
handleSaveFolder(onShareFolder);
|
onSaveFolder(onShareFolder);
|
||||||
} else {
|
} else {
|
||||||
onShareFolder();
|
onShareFolder();
|
||||||
}
|
}
|
||||||
@ -204,15 +189,17 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
limit: 'chatlistInvites',
|
limit: 'chatlistInvites',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [handleSaveFolder, invites, lang, maxInviteLinks, onShareFolder, state.folderId, state.isTouched]);
|
}, [
|
||||||
|
invites, state.folderId, state.isTouched, maxInviteLinks, isCreating, onSaveFolder, onShareFolder, lang,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleEditInviteClick = useCallback((e: React.MouseEvent<HTMLElement>, url: string) => {
|
const handleEditInviteClick = useCallback((e: React.MouseEvent<HTMLElement>, url: string) => {
|
||||||
if (state.isTouched) {
|
if (state.isTouched) {
|
||||||
handleSaveFolder(() => onOpenInvite(url));
|
onSaveFolder(() => onOpenInvite(url));
|
||||||
} else {
|
} else {
|
||||||
onOpenInvite(url);
|
onOpenInvite(url);
|
||||||
}
|
}
|
||||||
}, [handleSaveFolder, onOpenInvite, state.isTouched]);
|
}, [onSaveFolder, onOpenInvite, state.isTouched]);
|
||||||
|
|
||||||
function renderChatType(key: string, mode: 'included' | 'excluded') {
|
function renderChatType(key: string, mode: 'included' | 'excluded') {
|
||||||
const chatType = mode === 'included'
|
const chatType = mode === 'included'
|
||||||
@ -339,38 +326,36 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isCreating && (
|
<div className="settings-item pt-3">
|
||||||
<div className="settings-item pt-3">
|
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
{lang('FolderLinkScreen.Title')}
|
||||||
{lang('FolderLinkScreen.Title')}
|
</h4>
|
||||||
</h4>
|
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
className="settings-folders-list-item color-primary mb-0"
|
||||||
|
icon="add"
|
||||||
|
onClick={handleCreateInviteClick}
|
||||||
|
>
|
||||||
|
{lang('ChatListFilter.CreateLinkNew')}
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{invites?.map((invite) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
className="settings-folders-list-item color-primary mb-0"
|
className="settings-folders-list-item mb-0"
|
||||||
icon="add"
|
icon="link"
|
||||||
onClick={handleCreateInviteClick}
|
multiline
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={handleEditInviteClick}
|
||||||
|
clickArg={invite.url}
|
||||||
>
|
>
|
||||||
{lang('ChatListFilter.CreateLinkNew')}
|
<span className="title" dir="auto">{invite.title || invite.url}</span>
|
||||||
|
<span className="subtitle">
|
||||||
|
{lang('ChatListFilter.LinkLabelChatCount', invite.peerIds.length, 'i')}
|
||||||
|
</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
))}
|
||||||
|
|
||||||
{invites?.map((invite) => (
|
</div>
|
||||||
<ListItem
|
|
||||||
className="settings-folders-list-item mb-0"
|
|
||||||
icon="link"
|
|
||||||
multiline
|
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
|
||||||
onClick={handleEditInviteClick}
|
|
||||||
clickArg={invite.url}
|
|
||||||
>
|
|
||||||
<span className="title" dir="auto">{invite.title || invite.url}</span>
|
|
||||||
<span className="subtitle">
|
|
||||||
{lang('ChatListFilter.LinkLabelChatCount', invite.peerIds.length, 'i')}
|
|
||||||
</span>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
@ -93,13 +93,19 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [selectedIds, setSelectedIds] = useState<string[]>(peerIds || []);
|
const [selectedIds, setSelectedIds] = useState<string[]>(peerIds || []);
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevIsLoading]) => {
|
const isFirstRenderRef = useRef(true);
|
||||||
if (isLoading && !prevIsLoading) {
|
useEffectWithPrevDeps(([prevUrl]) => {
|
||||||
|
if (prevUrl !== url) {
|
||||||
|
isFirstRenderRef.current = true;
|
||||||
|
}
|
||||||
|
if (!isFirstRenderRef.current) return;
|
||||||
|
isFirstRenderRef.current = false;
|
||||||
|
if (!url) {
|
||||||
setSelectedIds(unlockedIds);
|
setSelectedIds(unlockedIds);
|
||||||
} else if (peerIds) {
|
} else if (peerIds) {
|
||||||
setSelectedIds(peerIds);
|
setSelectedIds(peerIds);
|
||||||
}
|
}
|
||||||
}, [isLoading, unlockedIds, peerIds]);
|
}, [url, unlockedIds, peerIds]);
|
||||||
|
|
||||||
const handleClickDisabled = useCallback((id: string) => {
|
const handleClickDisabled = useCallback((id: string) => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
@ -135,9 +141,10 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
}, [folderId, selectedIds, url]);
|
}, [folderId, selectedIds, url]);
|
||||||
|
|
||||||
const chatsCount = selectedIds.length;
|
const chatsCount = selectedIds.length;
|
||||||
|
const isDisabled = !chatsCount || isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-content no-border custom-scroll">
|
<div className="settings-content no-border custom-scroll SettingsFoldersChatsPicker">
|
||||||
<div className="settings-content-header">
|
<div className="settings-content-header">
|
||||||
<AnimatedIcon
|
<AnimatedIcon
|
||||||
size={STICKER_SIZE_FOLDER_SETTINGS}
|
size={STICKER_SIZE_FOLDER_SETTINGS}
|
||||||
@ -154,6 +161,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
<InviteLink
|
<InviteLink
|
||||||
inviteLink={isLoading ? lang('Loading') : url!}
|
inviteLink={isLoading ? lang('Loading') : url!}
|
||||||
onRevoke={handleRevoke}
|
onRevoke={handleRevoke}
|
||||||
|
isDisabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="settings-item settings-item-chatlist">
|
<div className="settings-item settings-item-chatlist">
|
||||||
@ -163,12 +171,13 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
onSelectedIdsChange={handleSelectedIdsChange}
|
onSelectedIdsChange={handleSelectedIdsChange}
|
||||||
selectedIds={selectedIds}
|
selectedIds={selectedIds}
|
||||||
onDisabledClick={handleClickDisabled}
|
onDisabledClick={handleClickDisabled}
|
||||||
|
isRoundCheckbox
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
isShown={isLoading || isTouched}
|
isShown={isLoading || isTouched}
|
||||||
disabled={isLoading}
|
disabled={isDisabled}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
ariaLabel="Save changes"
|
ariaLabel="Save changes"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -764,12 +764,29 @@ addActionHandler('addChatFolder', async (global, actions, payload): Promise<void
|
|||||||
// Clear fields from recommended folders
|
// Clear fields from recommended folders
|
||||||
const { id: recommendedId, description, ...newFolder } = folder;
|
const { id: recommendedId, description, ...newFolder } = folder;
|
||||||
|
|
||||||
|
const newId = maxId + 1;
|
||||||
|
const folderUpdate = {
|
||||||
|
id: newId,
|
||||||
|
...newFolder,
|
||||||
|
};
|
||||||
await callApi('editChatFolder', {
|
await callApi('editChatFolder', {
|
||||||
id: maxId + 1,
|
id: newId,
|
||||||
folderUpdate: {
|
folderUpdate,
|
||||||
id: maxId + 1,
|
});
|
||||||
...newFolder,
|
|
||||||
|
// Update called from the above `callApi` is throttled, but we need to apply changes immediately
|
||||||
|
actions.apiUpdate({
|
||||||
|
'@type': 'updateChatFolder',
|
||||||
|
id: newId,
|
||||||
|
folder: folderUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.requestNextSettingsScreen({
|
||||||
|
foldersAction: {
|
||||||
|
type: 'setFolderId',
|
||||||
|
payload: maxId + 1,
|
||||||
},
|
},
|
||||||
|
tabId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!description) {
|
if (!description) {
|
||||||
@ -2019,33 +2036,43 @@ addActionHandler('editChatlistInvite', async (global, actions, payload): Promise
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const result = await callApi('editChatlistInvite', { folderId, slug, peers });
|
try {
|
||||||
|
const result = await callApi('editChatlistInvite', { folderId, slug, peers });
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = {
|
global = {
|
||||||
...global,
|
...global,
|
||||||
chatFolders: {
|
chatFolders: {
|
||||||
...global.chatFolders,
|
...global.chatFolders,
|
||||||
invites: {
|
invites: {
|
||||||
...global.chatFolders.invites,
|
...global.chatFolders.invites,
|
||||||
[folderId]: global.chatFolders.invites[folderId]?.map((invite) => {
|
[folderId]: global.chatFolders.invites[folderId]?.map((invite) => {
|
||||||
if (invite.url === url) {
|
if (invite.url === url) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return invite;
|
return invite;
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
setGlobal(global);
|
||||||
global = updateTabState(global, {
|
} catch (error) {
|
||||||
shareFolderScreen: {
|
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
|
||||||
...selectTabState(global, tabId).shareFolderScreen!,
|
} finally {
|
||||||
isLoading: false,
|
global = getGlobal();
|
||||||
},
|
|
||||||
}, tabId);
|
global = updateTabState(global, {
|
||||||
setGlobal(global);
|
shareFolderScreen: {
|
||||||
|
...selectTabState(global, tabId).shareFolderScreen!,
|
||||||
|
isLoading: false,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('deleteChatlistInvite', async (global, actions, payload): Promise<void> => {
|
addActionHandler('deleteChatlistInvite', async (global, actions, payload): Promise<void> => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ApiUpdateChat } from '../../../api/types';
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { ALL_FOLDER_ID, 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 {
|
import {
|
||||||
@ -271,6 +271,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
...global.chatFolders,
|
...global.chatFolders,
|
||||||
byId: newChatFoldersById,
|
byId: newChatFoldersById,
|
||||||
orderedIds: newOrderedIds,
|
orderedIds: newOrderedIds,
|
||||||
|
invites: omit(global.chatFolders.invites, [id]),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -252,7 +252,7 @@ export function selectCanInviteToChat<T extends GlobalState>(global: T, chatId:
|
|||||||
if (!chat) return false;
|
if (!chat) return false;
|
||||||
|
|
||||||
// https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6
|
// https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6
|
||||||
return Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
|
return !chat.migratedTo && Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
|
||||||
chat.isCreator || getHasAdminRight(chat, 'inviteUsers')
|
chat.isCreator || getHasAdminRight(chat, 'inviteUsers')
|
||||||
|| (chat.usernames?.length && !chat.isJoinRequest)
|
|| (chat.usernames?.length && !chat.isJoinRequest)
|
||||||
) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers'))));
|
) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers'))));
|
||||||
|
|||||||
@ -122,9 +122,10 @@ export type FoldersState = {
|
|||||||
};
|
};
|
||||||
export type FoldersActions = (
|
export type FoldersActions = (
|
||||||
'setTitle' | 'saveFilters' | 'editFolder' | 'reset' | 'setChatFilter' | 'setIsLoading' | 'setError' |
|
'setTitle' | 'saveFilters' | 'editFolder' | 'reset' | 'setChatFilter' | 'setIsLoading' | 'setError' |
|
||||||
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters'
|
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters' | 'setIsTouched' |
|
||||||
|
'setFolderId' | 'setIsChatlist'
|
||||||
);
|
);
|
||||||
export type FolderEditDispatch = Dispatch<FoldersActions>;
|
export type FolderEditDispatch = Dispatch<FoldersState, FoldersActions>;
|
||||||
|
|
||||||
const INITIAL_STATE: FoldersState = {
|
const INITIAL_STATE: FoldersState = {
|
||||||
mode: 'create',
|
mode: 'create',
|
||||||
@ -150,6 +151,12 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
|||||||
},
|
},
|
||||||
isTouched: true,
|
isTouched: true,
|
||||||
};
|
};
|
||||||
|
case 'setFolderId':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
folderId: action.payload,
|
||||||
|
mode: 'edit',
|
||||||
|
};
|
||||||
case 'editIncludeFilters':
|
case 'editIncludeFilters':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -221,6 +228,12 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
|||||||
chatFilter: action.payload,
|
chatFilter: action.payload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'setIsTouched': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isTouched: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
case 'setIsLoading': {
|
case 'setIsLoading': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -230,9 +243,18 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
|||||||
case 'setError': {
|
case 'setError': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
isLoading: false,
|
||||||
error: action.payload,
|
error: action.payload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'setIsChatlist':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
folder: {
|
||||||
|
...state.folder,
|
||||||
|
isChatList: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
case 'reset':
|
case 'reset':
|
||||||
return INITIAL_STATE;
|
return INITIAL_STATE;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export type FormActions = (
|
|||||||
'changeBillingZip' | 'changeSaveInfo' | 'changeSaveCredentials' | 'setFormErrors' | 'resetState' | 'setTipAmount' |
|
'changeBillingZip' | 'changeSaveInfo' | 'changeSaveCredentials' | 'setFormErrors' | 'resetState' | 'setTipAmount' |
|
||||||
'changeSavedCredentialId'
|
'changeSavedCredentialId'
|
||||||
);
|
);
|
||||||
export type FormEditDispatch = Dispatch<FormActions>;
|
export type FormEditDispatch = Dispatch<FormState, FormActions>;
|
||||||
|
|
||||||
const INITIAL_STATE: FormState = {
|
const INITIAL_STATE: FormState = {
|
||||||
streetLine1: '',
|
streetLine1: '',
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import useReducer from '../useReducer';
|
|||||||
export type TwoFaActions = (
|
export type TwoFaActions = (
|
||||||
'setCurrentPassword' | 'setPassword' | 'setHint' | 'setEmail' | 'reset'
|
'setCurrentPassword' | 'setPassword' | 'setHint' | 'setEmail' | 'reset'
|
||||||
);
|
);
|
||||||
export type TwoFaDispatch = Dispatch<TwoFaActions>;
|
export type TwoFaDispatch = Dispatch<TwoFaState, TwoFaActions>;
|
||||||
|
|
||||||
export type TwoFaState = {
|
export type TwoFaState = {
|
||||||
currentPassword: string;
|
currentPassword: string;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import useForceUpdate from './useForceUpdate';
|
|||||||
|
|
||||||
export type ReducerAction<Actions> = { type: Actions; payload?: any };
|
export type ReducerAction<Actions> = { type: Actions; payload?: any };
|
||||||
export type StateReducer<State, Actions> = (state: State, action: ReducerAction<Actions>) => State;
|
export type StateReducer<State, Actions> = (state: State, action: ReducerAction<Actions>) => State;
|
||||||
export type Dispatch<Actions> = (action: ReducerAction<Actions>) => void;
|
export type Dispatch<State, Actions> = (action: ReducerAction<Actions>) => State;
|
||||||
|
|
||||||
export default function useReducer<State, Actions>(
|
export default function useReducer<State, Actions>(
|
||||||
reducer: StateReducer<State, Actions>,
|
reducer: StateReducer<State, Actions>,
|
||||||
@ -17,10 +17,11 @@ export default function useReducer<State, Actions>(
|
|||||||
const dispatch = useCallback((action: ReducerAction<Actions>) => {
|
const dispatch = useCallback((action: ReducerAction<Actions>) => {
|
||||||
state.current = reducerRef.current(state.current, action);
|
state.current = reducerRef.current(state.current, action);
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
|
return state.current;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
state.current,
|
state.current,
|
||||||
dispatch,
|
dispatch,
|
||||||
] as [State, Dispatch<Actions>];
|
] as [State, Dispatch<State, Actions>];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,8 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
|||||||
FRESH_CHANGE_ADMINS_FORBIDDEN: 'You were just elected admin, you can\'t add or modify other admins yet',
|
FRESH_CHANGE_ADMINS_FORBIDDEN: 'You were just elected admin, you can\'t add or modify other admins yet',
|
||||||
INPUT_USER_DEACTIVATED: 'The specified user was deleted',
|
INPUT_USER_DEACTIVATED: 'The specified user was deleted',
|
||||||
BOT_PRECHECKOUT_TIMEOUT: 'The request for payment has expired',
|
BOT_PRECHECKOUT_TIMEOUT: 'The request for payment has expired',
|
||||||
|
|
||||||
|
PEERS_LIST_EMPTY: 'No chats are added to the list',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user