Skip to content
This repository was archived by the owner on Oct 30, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"debugLogExplanation": "This log will be saved to your desktop.",
"reportIssue": "Report a Bug",
"markAllAsRead": "Mark All as Read",
"allMarkedAsRead": "All conversations marked read.",
"incomingError": "Error handling incoming message",
"media": "Media",
"mediaEmptyState": "No media",
Expand Down
53 changes: 53 additions & 0 deletions ts/components/dialog/MarkAllAsReadDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useState } from 'react';
import { SpacerLG } from '../basic/Text';
import { getConversationController } from '../../session/conversations';
import { markAllAsReadModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { ToastUtils } from '../../session/utils';

export const MarkAllAsReadDialog = () => {
const titleText = window.i18n('markAllAsRead');
const okText = window.i18n('markAllAsRead');
const cancelText = window.i18n('cancel');
const [_isLoading, setIsLoading] = useState(false);

const onClickOK = async () => {
setIsLoading(true);

const controller = getConversationController();
const convos = controller.getConversations().filter(conversation => {
return conversation.isApproved();
});
for (const convo of convos) {
await controller.get(convo.id).markAllAsRead();
}
ToastUtils.pushToastSuccess( 'allMarkedRead', window.i18n('allMarkedAsRead'));

setIsLoading(false);
closeDialog();
};

const closeDialog = () => {
window.inboxStore?.dispatch(markAllAsReadModal(null));
};

return (
<SessionWrapperModal title={titleText} onClose={closeDialog}>
<SpacerLG />

<div className="session-modal__button-group">
<SessionButton
text={okText}
buttonType={SessionButtonType.Simple}
onClick={onClickOK}
/>
<SessionButton
text={cancelText}
buttonType={SessionButtonType.Simple}
onClick={closeDialog}
/>
</div>
</SessionWrapperModal>
);
};
4 changes: 4 additions & 0 deletions ts/components/dialog/ModalContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getDeleteAccountModalState,
getEditProfileDialog,
getInviteContactModal,
getMarkAllAsReadDialog,
getOnionPathDialog,
getReactClearAllDialog,
getReactListDialog,
Expand Down Expand Up @@ -36,6 +37,7 @@ import { SessionNicknameDialog } from './SessionNicknameDialog';
import { BanOrUnBanUserDialog } from './BanOrUnbanUserDialog';
import { ReactListModal } from './ReactListModal';
import { ReactClearAllModal } from './ReactClearAllModal';
import { MarkAllAsReadDialog } from './MarkAllAsReadDialog';

export const ModalContainer = () => {
const confirmModalState = useSelector(getConfirmModal);
Expand All @@ -55,6 +57,7 @@ export const ModalContainer = () => {
const banOrUnbanUserModalState = useSelector(getBanOrUnbanUserModalState);
const reactListModalState = useSelector(getReactListDialog);
const reactClearAllModalState = useSelector(getReactClearAllDialog);
const markAllAsReadModalState = useSelector(getMarkAllAsReadDialog);

return (
<>
Expand All @@ -79,6 +82,7 @@ export const ModalContainer = () => {
{confirmModalState && <SessionConfirm {...confirmModalState} />}
{reactListModalState && <ReactListModal {...reactListModalState} />}
{reactClearAllModalState && <ReactClearAllModal {...reactClearAllModalState} />}
{markAllAsReadModalState && <MarkAllAsReadDialog {...markAllAsReadModalState} />}
</>
);
};
3 changes: 3 additions & 0 deletions ts/components/icon/SessionIconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface SProps extends SessionIconProps {
dataTestId?: string;
id?: string;
style?: object;
title?: string;
}

const StyledSessionIconButton = styled.div<{ color?: string; isSelected?: boolean }>`
Expand Down Expand Up @@ -55,6 +56,7 @@ const SessionIconButtonInner = React.forwardRef<HTMLDivElement, SProps>((props,
id,
dataTestId,
style,
title
} = props;
const clickHandler = (e: React.MouseEvent<HTMLDivElement>) => {
if (props.onClick) {
Expand All @@ -65,6 +67,7 @@ const SessionIconButtonInner = React.forwardRef<HTMLDivElement, SProps>((props,

return (
<StyledSessionIconButton
title={title}
color={iconColor}
isSelected={isSelected}
className={classNames('session-icon-button', iconSize)}
Expand Down
19 changes: 18 additions & 1 deletion ts/components/leftpane/ActionsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachm

import { DURATION } from '../../session/constants';

import { editProfileModal, onionPathModal } from '../../state/ducks/modalDialog';
import { editProfileModal,
markAllAsReadModal,
onionPathModal
} from '../../state/ducks/modalDialog';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { debounce, isEmpty, isString } from 'lodash';

Expand Down Expand Up @@ -78,6 +81,8 @@ const Section = (props: { type: SectionType }) => {
} else if (type === SectionType.PathIndicator) {
// Show Path Indicator Modal
dispatch(onionPathModal({}));
} else if (type === SectionType.MarkAllAsRead) {
dispatch(markAllAsReadModal({}));
} else {
// message section
dispatch(clearSearch());
Expand Down Expand Up @@ -111,6 +116,17 @@ const Section = (props: { type: SectionType }) => {
isSelected={isSelected}
/>
);
case SectionType.MarkAllAsRead:
return (
<SessionIconButton
title={window.i18n('markAllAsRead')}
iconSize="medium"
dataTestId="markallasread-section"
iconType={'check'}
onClick={handleClick}
isSelected={isSelected}
/>
);
case SectionType.Settings:
return (
<SessionIconButton
Expand Down Expand Up @@ -287,6 +303,7 @@ export const ActionsPanel = () => {
<LeftPaneSectionContainer data-testid="leftpane-section-container">
<Section type={SectionType.Profile} />
<Section type={SectionType.Message} />
<Section type={SectionType.MarkAllAsRead} />
<Section type={SectionType.Settings} />

<Section type={SectionType.PathIndicator} />
Expand Down
7 changes: 7 additions & 0 deletions ts/state/ducks/modalDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type EditProfileModalState = {} | null;
export type OnionPathModalState = EditProfileModalState;
export type RecoveryPhraseModalState = EditProfileModalState;
export type DeleteAccountModalState = EditProfileModalState;
export type MarkAllAsReadModalState = EditProfileModalState;

export type SessionPasswordModalState = { passwordAction: PasswordAction; onOk: () => void } | null;

Expand Down Expand Up @@ -50,6 +51,7 @@ export type ModalState = {
adminLeaveClosedGroup: AdminLeaveClosedGroupModalState;
sessionPasswordModal: SessionPasswordModalState;
deleteAccountModal: DeleteAccountModalState;
markAllAsReadModal: MarkAllAsReadModalState;
reactListModalState: ReactModalsState;
reactClearAllModalState: ReactModalsState;
};
Expand All @@ -70,6 +72,7 @@ export const initialModalState: ModalState = {
adminLeaveClosedGroup: null,
sessionPasswordModal: null,
deleteAccountModal: null,
markAllAsReadModal: null,
reactListModalState: null,
reactClearAllModalState: null,
};
Expand Down Expand Up @@ -123,6 +126,9 @@ const ModalSlice = createSlice({
updateDeleteAccountModal(state, action: PayloadAction<DeleteAccountModalState>) {
return { ...state, deleteAccountModal: action.payload };
},
markAllAsReadModal(state, action: PayloadAction<MarkAllAsReadModalState>) {
return { ...state, markAllAsReadModal: action.payload };
},
updateReactListModal(state, action: PayloadAction<ReactModalsState>) {
return { ...state, reactListModalState: action.payload };
},
Expand All @@ -147,6 +153,7 @@ export const {
recoveryPhraseModal,
adminLeaveClosedGroup,
sessionPassword,
markAllAsReadModal,
updateDeleteAccountModal,
updateBanOrUnbanUserModal,
updateReactListModal,
Expand Down
1 change: 1 addition & 0 deletions ts/state/ducks/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum SectionType {
Profile,
Message,
Settings,
MarkAllAsRead,
ColorMode,
PathIndicator,
}
Expand Down
5 changes: 5 additions & 0 deletions ts/state/selectors/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DeleteAccountModalState,
EditProfileModalState,
InviteContactModalState,
MarkAllAsReadModalState,
ModalState,
OnionPathModalState,
ReactModalsState,
Expand Down Expand Up @@ -109,3 +110,7 @@ export const getReactClearAllDialog = createSelector(
getModal,
(state: ModalState): ReactModalsState => state.reactClearAllModalState
);

export const getMarkAllAsReadDialog = createSelector(
getModal,
(state: ModalState): MarkAllAsReadModalState => state.markAllAsReadModal);
1 change: 1 addition & 0 deletions ts/types/LocalizerKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ export type LocalizerKeys =
| 'cannotRemoveCreatorFromGroup'
| 'editMenuCut'
| 'markAllAsRead'
| 'allMarkedAsRead'
| 'failedResolveOns'
| 'showDebugLog'
| 'declineRequestMessage'
Expand Down