diff --git a/commafeed-client/src/app/entries/slice.ts b/commafeed-client/src/app/entries/slice.ts index 8762e489..208719be 100644 --- a/commafeed-client/src/app/entries/slice.ts +++ b/commafeed-client/src/app/entries/slice.ts @@ -28,6 +28,7 @@ interface EntriesState { loading: boolean search?: string scrollingToEntry: boolean + markAllAsReadConfirmationDialogOpen: boolean } const initialState: EntriesState = { @@ -41,6 +42,7 @@ const initialState: EntriesState = { hasMore: true, loading: false, scrollingToEntry: false, + markAllAsReadConfirmationDialogOpen: false, } export const entriesSlice = createSlice({ @@ -61,6 +63,9 @@ export const entriesSlice = createSlice({ setSearch: (state, action: PayloadAction) => { state.search = action.payload }, + setMarkAllAsReadConfirmationDialogOpen: (state, action: PayloadAction) => { + state.markAllAsReadConfirmationDialogOpen = action.payload + }, }, extraReducers: builder => { builder.addCase(markEntry.pending, (state, action) => { @@ -119,4 +124,4 @@ export const entriesSlice = createSlice({ }, }) -export const { setSearch } = entriesSlice.actions +export const { setSearch, setMarkAllAsReadConfirmationDialogOpen } = entriesSlice.actions diff --git a/commafeed-client/src/app/entries/thunks.ts b/commafeed-client/src/app/entries/thunks.ts index f32af338..958139f8 100644 --- a/commafeed-client/src/app/entries/thunks.ts +++ b/commafeed-client/src/app/entries/thunks.ts @@ -1,7 +1,7 @@ import { createAppAsyncThunk } from "app/async-thunk" import { client } from "app/client" import { Constants } from "app/constants" -import { type EntrySource, type EntrySourceType, entriesSlice, setSearch } from "app/entries/slice" +import { type EntrySource, type EntrySourceType, entriesSlice, setMarkAllAsReadConfirmationDialogOpen, setSearch } from "app/entries/slice" import type { RootState } from "app/store" import { reloadTree } from "app/tree/thunks" import type { Entry, MarkRequest, TagRequest } from "app/types" @@ -123,6 +123,32 @@ export const markAllEntries = createAppAsyncThunk( } ) +export const markAllAsReadWithConfirmationIfRequired = createAppAsyncThunk( + "entries/entry/markAllAsReadWithConfirmationIfRequired", + async (_, thunkApi) => { + const state = thunkApi.getState() + const source = state.entries.source + const entriesTimestamp = state.entries.timestamp ?? Date.now() + const markAllAsReadConfirmation = state.user.settings?.markAllAsReadConfirmation + + if (markAllAsReadConfirmation) { + thunkApi.dispatch(setMarkAllAsReadConfirmationDialogOpen(true)) + } else { + thunkApi.dispatch( + markAllEntries({ + sourceType: source.type, + req: { + id: source.id, + read: true, + olderThan: Date.now(), + insertedBefore: entriesTimestamp, + }, + }) + ) + } + } +) + export const starEntry = createAppAsyncThunk( "entries/entry/star", (arg: { entry: Entry; starred: boolean }) => { diff --git a/commafeed-client/src/components/MarkAllAsReadConfirmationDialog.tsx b/commafeed-client/src/components/MarkAllAsReadConfirmationDialog.tsx new file mode 100644 index 00000000..a6d1cffb --- /dev/null +++ b/commafeed-client/src/components/MarkAllAsReadConfirmationDialog.tsx @@ -0,0 +1,77 @@ +import { Trans } from "@lingui/react/macro" +import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" +import { setMarkAllAsReadConfirmationDialogOpen } from "app/entries/slice" +import { markAllEntries } from "app/entries/thunks" +import { useAppDispatch, useAppSelector } from "app/store" +import { useState } from "react" + +export function MarkAllAsReadConfirmationDialog() { + const [threshold, setThreshold] = useState(0) + const open = useAppSelector(state => state.entries.markAllAsReadConfirmationDialogOpen) + const source = useAppSelector(state => state.entries.source) + const sourceLabel = useAppSelector(state => state.entries.sourceLabel) + const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now() + const dispatch = useAppDispatch() + + const onConfirm = () => { + dispatch( + markAllEntries({ + sourceType: source.type, + req: { + id: source.id, + read: true, + olderThan: Date.now() - threshold * 24 * 60 * 60 * 1000, + insertedBefore: entriesTimestamp, + }, + }) + ) + dispatch(setMarkAllAsReadConfirmationDialogOpen(false)) + } + + return ( + dispatch(setMarkAllAsReadConfirmationDialogOpen(false))} + title={Mark all entries as read} + > + + + {threshold === 0 && ( + + Are you sure you want to mark all entries of {sourceLabel} as read? + + )} + {threshold > 0 && ( + + Are you sure you want to mark entries older than {threshold} days of {sourceLabel} as read? + + )} + + e.key === "Enter" && onConfirm()} + /> + + + + + + + ) +} diff --git a/commafeed-client/src/components/content/FeedEntries.tsx b/commafeed-client/src/components/content/FeedEntries.tsx index a1686432..174ddc1e 100644 --- a/commafeed-client/src/components/content/FeedEntries.tsx +++ b/commafeed-client/src/components/content/FeedEntries.tsx @@ -5,7 +5,7 @@ import { Constants } from "app/constants" import type { ExpendableEntry } from "app/entries/slice" import { loadMoreEntries, - markAllEntries, + markAllAsReadWithConfirmationIfRequired, markEntry, reloadEntries, selectEntry, @@ -275,17 +275,7 @@ export function FeedEntries() { }) useMousetrap("shift+a", () => { // mark all entries as read - dispatch( - markAllEntries({ - sourceType: source.type, - req: { - id: source.id, - read: true, - olderThan: Date.now(), - insertedBefore: entriesTimestamp, - }, - }) - ) + dispatch(markAllAsReadWithConfirmationIfRequired()) }) useMousetrap("g a", async () => await dispatch(redirectToRootCategory())) useMousetrap("f", () => dispatch(toggleSidebar())) diff --git a/commafeed-client/src/components/header/Header.tsx b/commafeed-client/src/components/header/Header.tsx index 26e1f8aa..0df73531 100644 --- a/commafeed-client/src/components/header/Header.tsx +++ b/commafeed-client/src/components/header/Header.tsx @@ -2,7 +2,7 @@ import { msg } from "@lingui/core/macro" import { useLingui } from "@lingui/react" import { Box, Center, CloseButton, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core" import { useForm } from "@mantine/form" -import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks" +import { markAllAsReadWithConfirmationIfRequired, reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { changeReadingMode, changeReadingOrder } from "app/user/thunks" import { ActionButton } from "components/ActionButton" @@ -14,6 +14,7 @@ import { useEffect } from "react" import { TbArrowDown, TbArrowUp, + TbChecks, TbExternalLink, TbEye, TbEyeOff, @@ -24,7 +25,6 @@ import { TbSortDescending, TbUser, } from "react-icons/tb" -import { MarkAllAsReadButton } from "./MarkAllAsReadButton" import { ProfileMenu } from "./ProfileMenu" function HeaderDivider() { @@ -111,7 +111,11 @@ export function Header() { label={msg`Refresh`} onClick={async () => await dispatch(reloadEntries())} /> - + } + label={msg`Mark all as read`} + onClick={() => dispatch(markAllAsReadWithConfirmationIfRequired())} + /> diff --git a/commafeed-client/src/components/header/MarkAllAsReadButton.tsx b/commafeed-client/src/components/header/MarkAllAsReadButton.tsx deleted file mode 100644 index 3f0842db..00000000 --- a/commafeed-client/src/components/header/MarkAllAsReadButton.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { msg } from "@lingui/core/macro" -import { Trans } from "@lingui/react/macro" - -import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core" -import { markAllEntries } from "app/entries/thunks" -import { useAppDispatch, useAppSelector } from "app/store" -import { ActionButton } from "components/ActionButton" -import { useState } from "react" -import { TbChecks } from "react-icons/tb" - -export function MarkAllAsReadButton(props: { iconSize: number }) { - const [opened, setOpened] = useState(false) - const [threshold, setThreshold] = useState(0) - const source = useAppSelector(state => state.entries.source) - const sourceLabel = useAppSelector(state => state.entries.sourceLabel) - const entriesTimestamp = useAppSelector(state => state.entries.timestamp) ?? Date.now() - const markAllAsReadConfirmation = useAppSelector(state => state.user.settings?.markAllAsReadConfirmation) - const dispatch = useAppDispatch() - - const buttonClicked = () => { - if (markAllAsReadConfirmation) { - setThreshold(0) - setOpened(true) - } else { - dispatch( - markAllEntries({ - sourceType: source.type, - req: { - id: source.id, - read: true, - olderThan: Date.now(), - insertedBefore: entriesTimestamp, - }, - }) - ) - } - } - - const onConfirm = () => { - setOpened(false) - dispatch( - markAllEntries({ - sourceType: source.type, - req: { - id: source.id, - read: true, - olderThan: Date.now() - threshold * 24 * 60 * 60 * 1000, - insertedBefore: entriesTimestamp, - }, - }) - ) - } - - return ( - <> - setOpened(false)} title={Mark all entries as read}> - - - {threshold === 0 && ( - - Are you sure you want to mark all entries of {sourceLabel} as read? - - )} - {threshold > 0 && ( - - Are you sure you want to mark entries older than {threshold} days of {sourceLabel} as read? - - )} - - e.key === "Enter" && onConfirm()} - /> - - - - - - - } label={msg`Mark all as read`} onClick={buttonClicked} /> - - ) -} diff --git a/commafeed-client/src/pages/app/Layout.tsx b/commafeed-client/src/pages/app/Layout.tsx index 1a322b0f..ef3c99a5 100644 --- a/commafeed-client/src/pages/app/Layout.tsx +++ b/commafeed-client/src/pages/app/Layout.tsx @@ -11,6 +11,7 @@ import { ActionButton } from "components/ActionButton" import { AnnouncementDialog } from "components/AnnouncementDialog" import { Loader } from "components/Loader" import { Logo } from "components/Logo" +import { MarkAllAsReadConfirmationDialog } from "components/MarkAllAsReadConfirmationDialog" import { OnDesktop } from "components/responsive/OnDesktop" import { OnMobile } from "components/responsive/OnMobile" import { useAppLoading } from "hooks/useAppLoading" @@ -216,6 +217,7 @@ export default function Layout(props: LayoutProps) { }> +