Add shortcuts for add/remove chat to chat folder

This commit is contained in:
Jamie
2025-10-17 13:40:00 -07:00
committed by GitHub
parent f26919d399
commit 7f5af1a698
19 changed files with 421 additions and 52 deletions

View File

@@ -439,6 +439,18 @@
"messageformat": "Edit folder",
"description": "Left Pane > Inbox > Chat List > Chat Folders > Item > Right-Click Context Menu > Open settings for current chat folder"
},
"icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderToggleChatsItem": {
"messageformat": "Add to folder",
"description": "Left Pane > Inbox > Chat List > Chat > Right-Click Context Menu > Add to folder (only when in 'All chats')"
},
"icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderToggleChatsMenu__CreateFolderItem": {
"messageformat": "Create folder",
"description": "Left Pane > Inbox > Chat List > Chat > Right-Click Context Menu > Add to folder (only when in 'All chats') > Create folder"
},
"icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderRemoveChatItem": {
"messageformat": "Remove from folder",
"description": "Left Pane > Inbox > Chat List > Chat > Right-Click Context Menu > Remove from folder (only when in custom chat folder)"
},
"icu:CountryCodeSelect__placeholder": {
"messageformat": "Country code",
"description": "Placeholder displayed as default value of country code select element"
@@ -888,6 +900,14 @@
"messageformat": "Error saving receipt. Please try again.",
"description": "Toast message shown when a donation receipt fails to save to disk"
},
"icu:Toast--ChatFolderAddedChat": {
"messageformat": "Added to “{chatFolderName}”",
"description": "Toast message show when a chat folder has a new chat added to it (via chats list context menu)"
},
"icu:Toast--ChatFolderRemovedChat": {
"messageformat": "Removed from “{chatFolderName}”",
"description": "Toast message show when a chat folder has a chat removed from it (via chats list context menu)"
},
"icu:Toast--ChatFolderCreated": {
"messageformat": "{chatFolderName} folder added",
"description": "Toast message show when a chat folder is created (in some places like creating a preset)"

View File

@@ -5069,7 +5069,8 @@ button.module-calling-participants-list__contact {
}
}
&:hover:not(:disabled, &--disabled, &--is-selected) {
&:hover:not(:disabled, &--disabled, &--is-selected),
&[data-state='open'] {
background-color: light-dark(
variables.$color-gray-05,
variables.$color-gray-75

View File

@@ -338,6 +338,9 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
<LeftPaneConversationListItemContextMenu
i18n={i18n}
conversation={getDefaultConversation()}
selectedChatFolder={null}
currentChatFolders={CurrentChatFolders.createEmpty()}
isActivelySearching={false}
onMarkUnread={action('onMarkUnread')}
onMarkRead={action('onMarkRead')}
onPin={action('onPin')}
@@ -346,6 +349,8 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
onArchive={action('onArchive')}
onUnarchive={action('onUnarchive')}
onDelete={action('onDelete')}
onChatFolderOpenCreatePage={action('onChatFolderOpenCreatePage')}
onChatFolderToggleChat={action('onChatFolderToggleChat')}
localDeleteWarningShown={false}
setLocalDeleteWarningShown={action('setLocalDeleteWarningShown')}
>

View File

@@ -674,6 +674,7 @@ EditChatFolder.args = {
settingsLocation: {
page: SettingsPage.EditChatFolder,
chatFolderId: null,
initChatFolderParams: null,
previousLocation: null,
},
};

View File

@@ -561,6 +561,7 @@ export function Preferences({
setSettingsLocation({
page: SettingsPage.EditChatFolder,
chatFolderId,
initChatFolderParams: null,
previousLocation: null,
});
},
@@ -2017,6 +2018,7 @@ export function Preferences({
previousLocation: settingsLocation.previousLocation,
settingsPaneRef,
existingChatFolderId: settingsLocation.chatFolderId,
initChatFolderParams: settingsLocation.initChatFolderParams,
});
} else if (settingsLocation.page === SettingsPage.PNP) {
let sharingDescription: string;

View File

@@ -61,6 +61,16 @@ function getToast(toastType: ToastType): AnyToast {
return { toastType: ToastType.CaptchaFailed };
case ToastType.CaptchaSolved:
return { toastType: ToastType.CaptchaSolved };
case ToastType.ChatFolderAddedChat:
return {
toastType: ToastType.ChatFolderCreated,
parameters: { chatFolderName: 'Friends' },
};
case ToastType.ChatFolderRemovedChat:
return {
toastType: ToastType.ChatFolderCreated,
parameters: { chatFolderName: 'Friends' },
};
case ToastType.ChatFolderCreated:
return {
toastType: ToastType.ChatFolderCreated,

View File

@@ -189,6 +189,34 @@ export function renderToast({
);
}
if (toastType === ToastType.ChatFolderAddedChat) {
return (
<Toast onClose={hideToast}>
<I18n
i18n={i18n}
id="icu:Toast--ChatFolderAddedChat"
components={{
chatFolderName: <UserText text={toast.parameters.chatFolderName} />,
}}
/>
</Toast>
);
}
if (toastType === ToastType.ChatFolderRemovedChat) {
return (
<Toast onClose={hideToast}>
<I18n
i18n={i18n}
id="icu:Toast--ChatFolderRemovedChat"
components={{
chatFolderName: <UserText text={toast.parameters.chatFolderName} />,
}}
/>
</Toast>
);
}
if (toastType === ToastType.ChatFolderCreated) {
return (
<Toast onClose={hideToast}>

View File

@@ -15,6 +15,20 @@ import { isAlpha } from '../../util/version.std.js';
import { drop } from '../../util/drop.std.js';
import { DeleteMessagesConfirmationDialog } from '../DeleteMessagesConfirmationDialog.dom.js';
import { getMuteOptions } from '../../util/getMuteOptions.std.js';
import {
CHAT_FOLDER_DEFAULTS,
ChatFolderType,
isConversationInChatFolder,
} from '../../types/ChatFolder.std.js';
import type {
ChatFolderParams,
ChatFolder,
ChatFolderId,
} from '../../types/ChatFolder.std.js';
import { CurrentChatFolders } from '../../types/CurrentChatFolders.std.js';
import { strictAssert } from '../../util/assert.std.js';
import { UserText } from '../UserText.dom.js';
import { isConversationMuted } from '../../util/isConversationMuted.std.js';
function isEnabled() {
const env = getEnvironment();
@@ -38,9 +52,18 @@ function isEnabled() {
return false;
}
export type ChatFolderToggleChat = (
chatFolderId: ChatFolderId,
conversationId: string,
toggle: boolean
) => void;
export type LeftPaneConversationListItemContextMenuProps = Readonly<{
i18n: LocalizerType;
conversation: ConversationType;
selectedChatFolder: ChatFolder | null;
currentChatFolders: CurrentChatFolders;
isActivelySearching: boolean;
onMarkUnread: (conversationId: string) => void;
onMarkRead: (conversationId: string) => void;
onPin: (conversationId: string) => void;
@@ -49,6 +72,8 @@ export type LeftPaneConversationListItemContextMenuProps = Readonly<{
onArchive: (conversationId: string) => void;
onUnarchive: (conversationId: string) => void;
onDelete: (conversationId: string) => void;
onChatFolderOpenCreatePage: (initChatFolderParams: ChatFolderParams) => void;
onChatFolderToggleChat: ChatFolderToggleChat;
localDeleteWarningShown: boolean;
setLocalDeleteWarningShown: () => void;
children: ReactNode;
@@ -59,6 +84,7 @@ export const LeftPaneConversationListItemContextMenu: FC<LeftPaneConversationLis
const {
i18n,
conversation,
selectedChatFolder,
onMarkUnread,
onMarkRead,
onPin,
@@ -67,9 +93,19 @@ export const LeftPaneConversationListItemContextMenu: FC<LeftPaneConversationLis
onArchive,
onUnarchive,
onDelete,
onChatFolderOpenCreatePage,
onChatFolderToggleChat,
} = props;
const { id: conversationId, muteExpiresAt } = conversation;
const selectedChatFolderId = selectedChatFolder?.id ?? null;
const isSelectedChatFolderAllChats = useMemo(() => {
return (
selectedChatFolder == null ||
selectedChatFolder.folderType === ChatFolderType.ALL
);
}, [selectedChatFolder]);
const muteOptions = useMemo(() => {
return getMuteOptions(muteExpiresAt, i18n);
@@ -125,6 +161,21 @@ export const LeftPaneConversationListItemContextMenu: FC<LeftPaneConversationLis
onDelete(conversationId);
}, [onDelete, conversationId]);
const handleChatFolderCreateNew = useCallback(() => {
onChatFolderOpenCreatePage({
...CHAT_FOLDER_DEFAULTS,
includedConversationIds: [conversationId],
});
}, [onChatFolderOpenCreatePage, conversationId]);
const handleChatFolderRemoveChat = useCallback(() => {
strictAssert(
selectedChatFolderId != null,
'Missing selectedChatFolderId'
);
onChatFolderToggleChat(selectedChatFolderId, conversationId, false);
}, [onChatFolderToggleChat, selectedChatFolderId, conversationId]);
return (
<>
<AxoContextMenu.Root>
@@ -175,6 +226,42 @@ export const LeftPaneConversationListItemContextMenu: FC<LeftPaneConversationLis
})}
</AxoContextMenu.SubContent>
</AxoContextMenu.Sub>
{!props.isActivelySearching &&
isSelectedChatFolderAllChats &&
props.currentChatFolders.hasAnyCurrentCustomChatFolders && (
<AxoContextMenu.Sub>
<AxoContextMenu.SubTrigger symbol="folder">
{i18n(
'icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderToggleChatsItem'
)}
</AxoContextMenu.SubTrigger>
<AxoContextMenu.SubContent>
<ContextMenuChatFolderToggleChatsSubMenu
currentChatFolders={props.currentChatFolders}
conversation={conversation}
onChatFolderToggleChat={props.onChatFolderToggleChat}
/>
<AxoContextMenu.Item
symbol="plus"
onSelect={handleChatFolderCreateNew}
>
{i18n(
'icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderToggleChatsMenu__CreateFolderItem'
)}
</AxoContextMenu.Item>
</AxoContextMenu.SubContent>
</AxoContextMenu.Sub>
)}
{!props.isActivelySearching && !isSelectedChatFolderAllChats && (
<AxoContextMenu.Item
symbol="folder"
onSelect={handleChatFolderRemoveChat}
>
{i18n(
'icu:LeftPane__ConversationListItem__ContextMenu__ChatFolderRemoveChatItem'
)}
</AxoContextMenu.Item>
)}
{!conversation.isArchived && (
<AxoContextMenu.Item symbol="archive" onSelect={handleArchive}>
{i18n('icu:archiveConversation')}
@@ -273,3 +360,73 @@ function ContextMenuCopyTextItem(props: {
</AxoContextMenu.Item>
);
}
function ContextMenuChatFolderToggleChatsSubMenu(props: {
currentChatFolders: CurrentChatFolders;
conversation: ConversationType;
onChatFolderToggleChat: ChatFolderToggleChat;
}) {
const { currentChatFolders } = props;
const sortedAndFilteredChatFolders = useMemo(() => {
return CurrentChatFolders.toSortedArray(currentChatFolders).filter(
chatFolder => {
return chatFolder.folderType === ChatFolderType.CUSTOM;
}
);
}, [currentChatFolders]);
return (
<>
{sortedAndFilteredChatFolders.map(chatFolder => {
return (
<ContextMenuChatFolderToggleChatItem
key={chatFolder.id}
chatFolder={chatFolder}
conversation={props.conversation}
onChatFolderToggleChat={props.onChatFolderToggleChat}
/>
);
})}
</>
);
}
function ContextMenuChatFolderToggleChatItem(props: {
chatFolder: ChatFolder;
conversation: ConversationType;
onChatFolderToggleChat: ChatFolderToggleChat;
}) {
const { chatFolder, conversation, onChatFolderToggleChat } = props;
const chatFolderId = chatFolder.id;
const conversationId = conversation.id;
const checked = useMemo(() => {
return isConversationInChatFolder(chatFolder, conversation, {
ignoreShowOnlyUnread: true,
ignoreShowMutedChats: true,
});
}, [chatFolder, conversation]);
const isExcludedByMute = useMemo(() => {
return !chatFolder.showMutedChats && isConversationMuted(conversation);
}, [chatFolder, conversation]);
const handleCheckedChange = useCallback(
(value: boolean) => {
onChatFolderToggleChat(chatFolderId, conversationId, value);
},
[onChatFolderToggleChat, chatFolderId, conversationId]
);
return (
<AxoContextMenu.CheckboxItem
symbol="folder"
checked={checked}
disabled={checked || isExcludedByMute}
onCheckedChange={handleCheckedChange}
>
<UserText text={chatFolder.name} />
</AxoContextMenu.CheckboxItem>
);
}

View File

@@ -172,6 +172,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
details: {
page: SettingsPage.EditChatFolder,
chatFolderId: selectedChatFolder.id,
initChatFolderParams: null,
previousLocation: {
tab: NavTab.Chats,
},

View File

@@ -1269,6 +1269,11 @@ type WritableInterface = {
createAllChatsChatFolder: () => ChatFolder;
upsertAllChatsChatFolderFromSync: (chatFolder: ChatFolder) => void;
updateChatFolder: (chatFolder: ChatFolder) => void;
updateChatFolderToggleChat: (
chatFolderId: ChatFolderId,
conversationId: string,
toggle: boolean
) => void;
updateChatFolderPositions: (chatFolders: ReadonlyArray<ChatFolder>) => void;
updateChatFolderDeletedAtTimestampMsFromSync: (
chatFolderId: ChatFolderId,

View File

@@ -246,6 +246,7 @@ import {
createAllChatsChatFolder,
upsertAllChatsChatFolderFromSync,
updateChatFolder,
updateChatFolderToggleChat,
markChatFolderDeleted,
getOldestDeletedChatFolder,
updateChatFolderPositions,
@@ -710,10 +711,11 @@ export const DataWriter: ServerWritableInterface = {
createAllChatsChatFolder,
upsertAllChatsChatFolderFromSync,
updateChatFolder,
markChatFolderDeleted,
deleteExpiredChatFolders,
updateChatFolderToggleChat,
updateChatFolderPositions,
updateChatFolderDeletedAtTimestampMsFromSync,
markChatFolderDeleted,
deleteExpiredChatFolders,
removeAll,
removeAllConfiguration,
@@ -8047,7 +8049,7 @@ function eraseStorageServiceState(db: WritableDB): void {
storageID = null,
storageVersion = null,
storageUnknownFields = null,
storageNeedsSync = 0;
storageNeedsSync = 0;
-- Notification Profiles
UPDATE notificationProfiles

View File

@@ -96,19 +96,17 @@ export function getCurrentChatFolders(
export function getChatFolder(
db: ReadableDB,
id: ChatFolderId
chatFolderId: ChatFolderId
): ChatFolder | null {
return db.transaction(() => {
const [query, params] = sql`
SELECT * FROM chatFolders
WHERE id = ${id};
`;
const row = db.prepare(query).get<ChatFolderRow>(params);
if (row == null) {
return null;
}
return rowToChatFolder(row);
})();
const [query, params] = sql`
SELECT * FROM chatFolders
WHERE id = ${chatFolderId};
`;
const row = db.prepare(query).get<ChatFolderRow>(params);
if (row == null) {
return null;
}
return rowToChatFolder(row);
}
function _insertChatFolder(db: WritableDB, chatFolder: ChatFolder): void {
@@ -224,30 +222,63 @@ export function upsertAllChatsChatFolderFromSync(
}
export function updateChatFolder(db: WritableDB, chatFolder: ChatFolder): void {
const chatFolderRow = chatFolderToRow(chatFolder);
const [chatFolderQuery, chatFolderParams] = sql`
UPDATE chatFolders
SET
id = ${chatFolderRow.id},
folderType = ${chatFolderRow.folderType},
name = ${chatFolderRow.name},
position = ${chatFolderRow.position},
showOnlyUnread = ${chatFolderRow.showOnlyUnread},
showMutedChats = ${chatFolderRow.showMutedChats},
includeAllIndividualChats = ${chatFolderRow.includeAllIndividualChats},
includeAllGroupChats = ${chatFolderRow.includeAllGroupChats},
includedConversationIds = ${chatFolderRow.includedConversationIds},
excludedConversationIds = ${chatFolderRow.excludedConversationIds},
deletedAtTimestampMs = ${chatFolderRow.deletedAtTimestampMs},
storageID = ${chatFolderRow.storageID},
storageVersion = ${chatFolderRow.storageVersion},
storageUnknownFields = ${chatFolderRow.storageUnknownFields},
storageNeedsSync = ${chatFolderRow.storageNeedsSync}
WHERE
id = ${chatFolderRow.id}
`;
db.prepare(chatFolderQuery).run(chatFolderParams);
}
export function updateChatFolderToggleChat(
db: WritableDB,
chatFolderId: ChatFolderId,
conversationId: string,
toggle: boolean
): void {
return db.transaction(() => {
const chatFolderRow = chatFolderToRow(chatFolder);
const [chatFolderQuery, chatFolderParams] = sql`
UPDATE chatFolders
SET
id = ${chatFolderRow.id},
folderType = ${chatFolderRow.folderType},
name = ${chatFolderRow.name},
position = ${chatFolderRow.position},
showOnlyUnread = ${chatFolderRow.showOnlyUnread},
showMutedChats = ${chatFolderRow.showMutedChats},
includeAllIndividualChats = ${chatFolderRow.includeAllIndividualChats},
includeAllGroupChats = ${chatFolderRow.includeAllGroupChats},
includedConversationIds = ${chatFolderRow.includedConversationIds},
excludedConversationIds = ${chatFolderRow.excludedConversationIds},
deletedAtTimestampMs = ${chatFolderRow.deletedAtTimestampMs},
storageID = ${chatFolderRow.storageID},
storageVersion = ${chatFolderRow.storageVersion},
storageUnknownFields = ${chatFolderRow.storageUnknownFields},
storageNeedsSync = ${chatFolderRow.storageNeedsSync}
WHERE
id = ${chatFolderRow.id}
`;
db.prepare(chatFolderQuery).run(chatFolderParams);
const chatFolder = getChatFolder(db, chatFolderId);
strictAssert(
chatFolder != null,
`Missing chat folder for id: ${chatFolderId}`
);
const included = new Set<string>(chatFolder.includedConversationIds);
const excluded = new Set<string>(chatFolder.excludedConversationIds);
if (toggle) {
// add
included.add(conversationId);
excluded.delete(conversationId);
} else {
// remove
included.delete(conversationId);
excluded.add(conversationId);
}
updateChatFolder(db, {
...chatFolder,
includedConversationIds: Array.from(included),
excludedConversationIds: Array.from(excluded),
storageNeedsSync: true,
});
})();
}

View File

@@ -207,6 +207,41 @@ function updateSelectedChangeFolderId(
};
}
function updateChatFolderToggleChat(
chatFolderId: ChatFolderId,
conversationId: string,
toggle: boolean,
showToastOnSuccess: boolean
): ThunkAction<void, RootStateType, unknown, ShowToastActionType> {
return async (dispatch, getState) => {
const currentChatFolders = getCurrentChatFolders(getState());
const chatFolder = CurrentChatFolders.expect(
currentChatFolders,
chatFolderId,
'updateChatFolderToggleChat'
);
await DataWriter.updateChatFolderToggleChat(
chatFolderId,
conversationId,
toggle
);
storageServiceUploadJob({ reason: 'toggleChatFolderChat' });
dispatch(_refetchChatFolders());
if (showToastOnSuccess) {
dispatch(
showToast({
toastType: toggle
? ToastType.ChatFolderAddedChat
: ToastType.ChatFolderRemovedChat,
parameters: { chatFolderName: chatFolder.name },
})
);
}
};
}
export const actions = {
refetchChatFolders,
createChatFolder,
@@ -215,6 +250,7 @@ export const actions = {
deleteChatFolder,
updateChatFoldersPositions,
updateSelectedChangeFolderId,
updateChatFolderToggleChat,
};
export const useChatFolderActions = (): BoundActionCreatorsMapObject<

View File

@@ -45,6 +45,7 @@ export const SmartLeftPaneChatFolders = memo(
details: {
page: SettingsPage.EditChatFolder,
chatFolderId,
initChatFolderParams: null,
previousLocation: location,
},
});

View File

@@ -6,18 +6,34 @@ import React, { memo, useCallback } from 'react';
import { useSelector } from 'react-redux';
import { getIntl } from '../selectors/user.std.js';
import { getConversationByIdSelector } from '../selectors/conversations.dom.js';
import type { ChatFolderToggleChat } from '../../components/leftPane/LeftPaneConversationListItemContextMenu.dom.js';
import { LeftPaneConversationListItemContextMenu } from '../../components/leftPane/LeftPaneConversationListItemContextMenu.dom.js';
import { strictAssert } from '../../util/assert.std.js';
import type { RenderConversationListItemContextMenuProps } from '../../components/conversationList/BaseConversationListItem.dom.js';
import { useConversationsActions } from '../ducks/conversations.preload.js';
import { getLocalDeleteWarningShown } from '../selectors/items.dom.js';
import { useItemsActions } from '../ducks/items.preload.js';
import {
getCurrentChatFolders,
getSelectedChatFolder,
} from '../selectors/chatFolders.std.js';
import { useChatFolderActions } from '../ducks/chatFolders.preload.js';
import { useNavActions } from '../ducks/nav.std.js';
import { NavTab, SettingsPage } from '../../types/Nav.std.js';
import type { ChatFolderParams } from '../../types/ChatFolder.std.js';
import { getSelectedLocation } from '../selectors/nav.preload.js';
import { getIsActivelySearching } from '../selectors/search.dom.js';
export const SmartLeftPaneConversationListItemContextMenu: FC<RenderConversationListItemContextMenuProps> =
memo(function SmartLeftPaneConversationListItemContextMenu(props) {
const i18n = useSelector(getIntl);
const conversationByIdSelector = useSelector(getConversationByIdSelector);
const localDeleteWarningShown = useSelector(getLocalDeleteWarningShown);
const location = useSelector(getSelectedLocation);
const isActivelySearching = useSelector(getIsActivelySearching);
const selectedChatFolder = useSelector(getSelectedChatFolder);
const currentChatFolders = useSelector(getCurrentChatFolders);
const {
onMarkUnread,
markConversationRead,
@@ -27,7 +43,9 @@ export const SmartLeftPaneConversationListItemContextMenu: FC<RenderConversation
deleteConversation,
setMuteExpiration,
} = useConversationsActions();
const { updateChatFolderToggleChat } = useChatFolderActions();
const { putItem } = useItemsActions();
const { changeLocation } = useNavActions();
const setLocalDeleteWarningShown = useCallback(() => {
putItem('localDeleteWarningShown', true);
@@ -50,10 +68,35 @@ export const SmartLeftPaneConversationListItemContextMenu: FC<RenderConversation
[setPinned]
);
const handleChatFolderOpenCreatePage = useCallback(
(initChatFolderParams: ChatFolderParams) => {
changeLocation({
tab: NavTab.Settings,
details: {
page: SettingsPage.EditChatFolder,
chatFolderId: null,
initChatFolderParams,
previousLocation: location,
},
});
},
[changeLocation, location]
);
const handleChatFolderToggleChat: ChatFolderToggleChat = useCallback(
(chatFolderId, conversationId, toggle) => {
updateChatFolderToggleChat(chatFolderId, conversationId, toggle, true);
},
[updateChatFolderToggleChat]
);
return (
<LeftPaneConversationListItemContextMenu
i18n={i18n}
conversation={conversation}
selectedChatFolder={selectedChatFolder}
currentChatFolders={currentChatFolders}
isActivelySearching={isActivelySearching}
onMarkUnread={onMarkUnread}
onMarkRead={markConversationRead}
onPin={handlePin}
@@ -62,6 +105,8 @@ export const SmartLeftPaneConversationListItemContextMenu: FC<RenderConversation
onArchive={onArchive}
onUnarchive={onMoveToInbox}
onDelete={deleteConversation}
onChatFolderOpenCreatePage={handleChatFolderOpenCreatePage}
onChatFolderToggleChat={handleChatFolderToggleChat}
localDeleteWarningShown={localDeleteWarningShown}
setLocalDeleteWarningShown={setLocalDeleteWarningShown}
>

View File

@@ -5,6 +5,7 @@ import { useSelector } from 'react-redux';
import type { PreferencesEditChatFolderPageProps } from '../../components/preferences/chatFolders/PreferencesEditChatFoldersPage.dom.js';
import { PreferencesEditChatFolderPage } from '../../components/preferences/chatFolders/PreferencesEditChatFoldersPage.dom.js';
import { getIntl, getTheme } from '../selectors/user.std.js';
import type { ChatFolderParams } from '../../types/ChatFolder.std.js';
import { CHAT_FOLDER_DEFAULTS } from '../../types/ChatFolder.std.js';
import {
getAllComposableConversations,
@@ -20,13 +21,14 @@ import { CurrentChatFolders } from '../../types/CurrentChatFolders.std.js';
export type SmartPreferencesEditChatFolderPageProps = Readonly<{
previousLocation: Location | null;
existingChatFolderId: PreferencesEditChatFolderPageProps['existingChatFolderId'];
initChatFolderParams: ChatFolderParams | null;
settingsPaneRef: PreferencesEditChatFolderPageProps['settingsPaneRef'];
}>;
export function SmartPreferencesEditChatFolderPage(
props: SmartPreferencesEditChatFolderPageProps
): JSX.Element {
const { existingChatFolderId } = props;
const { existingChatFolderId, initChatFolderParams } = props;
const i18n = useSelector(getIntl);
const theme = useSelector(getTheme);
@@ -38,23 +40,23 @@ export function SmartPreferencesEditChatFolderPage(
useChatFolderActions();
const { changeLocation } = useNavActions();
const initChatFolderParams = useMemo(() => {
const initChatFolderParamsOrCurrentChatFolder = useMemo(() => {
if (existingChatFolderId == null) {
return CHAT_FOLDER_DEFAULTS;
return initChatFolderParams ?? CHAT_FOLDER_DEFAULTS;
}
return CurrentChatFolders.expect(
currentChatFolders,
existingChatFolderId,
'initChatFolderParams'
);
}, [currentChatFolders, existingChatFolderId]);
}, [currentChatFolders, existingChatFolderId, initChatFolderParams]);
return (
<PreferencesEditChatFolderPage
i18n={i18n}
previousLocation={props.previousLocation}
existingChatFolderId={props.existingChatFolderId}
initChatFolderParams={initChatFolderParams}
initChatFolderParams={initChatFolderParamsOrCurrentChatFolder}
changeLocation={changeLocation}
conversations={conversations}
preferredBadgeSelector={preferredBadgeSelector}

View File

@@ -153,6 +153,11 @@ type ConversationPropsForChatFolder = Pick<
'type' | 'id' | 'unreadCount' | 'markedUnread' | 'muteExpiresAt'
>;
export type ChatFolderConversationFilterOptions = Readonly<{
ignoreShowOnlyUnread?: boolean;
ignoreShowMutedChats?: boolean;
}>;
function _isConversationIncludedInChatFolder(
chatFolder: ChatFolder,
conversation: ConversationPropsForChatFolder
@@ -171,13 +176,18 @@ function _isConversationIncludedInChatFolder(
function _isConversationExcludedFromChatFolder(
chatFolder: ChatFolder,
conversation: ConversationPropsForChatFolder
conversation: ConversationPropsForChatFolder,
options: ChatFolderConversationFilterOptions
): boolean {
if (chatFolder.showOnlyUnread && !isConversationUnread(conversation)) {
return true; // not unread, only showing unread
if (!options.ignoreShowOnlyUnread) {
if (chatFolder.showOnlyUnread && !isConversationUnread(conversation)) {
return true; // not unread, only showing unread
}
}
if (!chatFolder.showMutedChats && (conversation.muteExpiresAt ?? 0) > 0) {
return true; // muted, not showing muted chats
if (!options.ignoreShowMutedChats) {
if (!chatFolder.showMutedChats && (conversation.muteExpiresAt ?? 0) > 0) {
return true; // muted, not showing muted chats
}
}
if (chatFolder.excludedConversationIds.includes(conversation.id)) {
return true; // is excluded by id
@@ -187,7 +197,8 @@ function _isConversationExcludedFromChatFolder(
export function isConversationInChatFolder(
chatFolder: ChatFolder,
conversation: ConversationPropsForChatFolder
conversation: ConversationPropsForChatFolder,
options: ChatFolderConversationFilterOptions = {}
): boolean {
if (chatFolder.folderType === ChatFolderType.ALL) {
return true;
@@ -195,6 +206,6 @@ export function isConversationInChatFolder(
return (
_isConversationIncludedInChatFolder(chatFolder, conversation) &&
!_isConversationExcludedFromChatFolder(chatFolder, conversation)
!_isConversationExcludedFromChatFolder(chatFolder, conversation, options)
);
}

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyDeep } from 'type-fest';
import type { ChatFolderId } from './ChatFolder.std.js';
import type { ChatFolderId, ChatFolderParams } from './ChatFolder.std.js';
export type SettingsLocation = ReadonlyDeep<
| {
@@ -16,6 +16,7 @@ export type SettingsLocation = ReadonlyDeep<
| {
page: SettingsPage.EditChatFolder;
chatFolderId: ChatFolderId | null;
initChatFolderParams: ChatFolderParams | null;
previousLocation: Location | null;
}
| {

View File

@@ -20,6 +20,8 @@ export enum ToastType {
CannotOpenGiftBadgeOutgoing = 'CannotOpenGiftBadgeOutgoing',
CannotStartGroupCall = 'CannotStartGroupCall',
ChatFolderCreated = 'ChatFolderCreated',
ChatFolderAddedChat = 'ChatFolderAddedChat',
ChatFolderRemovedChat = 'ChatFolderRemovedChat',
ConversationArchived = 'ConversationArchived',
ConversationMarkedUnread = 'ConversationMarkedUnread',
ConversationRemoved = 'ConversationRemoved',
@@ -118,6 +120,14 @@ export type AnyToast =
| { toastType: ToastType.CannotStartGroupCall }
| { toastType: ToastType.CaptchaFailed }
| { toastType: ToastType.CaptchaSolved }
| {
toastType: ToastType.ChatFolderAddedChat;
parameters: { chatFolderName: string };
}
| {
toastType: ToastType.ChatFolderRemovedChat;
parameters: { chatFolderName: string };
}
| {
toastType: ToastType.ChatFolderCreated;
parameters: { chatFolderName: string };