From 973fe56cc8fdc761abd58012468e28c38605888f Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 19 Aug 2022 10:34:04 +0200 Subject: [PATCH] add support for starring entries --- commafeed-client/src/App.tsx | 2 +- commafeed-client/src/app/client.ts | 2 ++ commafeed-client/src/app/constants.ts | 24 ++++++++++++++++--- commafeed-client/src/app/slices/entries.ts | 16 ++++++++++++- commafeed-client/src/app/slices/redirect.ts | 2 +- .../components/content/FeedEntryFooter.tsx | 9 +++++-- .../components/content/add/CategorySelect.tsx | 4 ++-- .../src/components/content/add/Subscribe.tsx | 2 +- .../src/components/sidebar/Tree.tsx | 21 +++++++++++++--- commafeed-client/src/locales/en/messages.po | 14 +++++++++++ commafeed-client/src/locales/fr/messages.po | 14 +++++++++++ commafeed-client/src/pages/app/AboutPage.tsx | 2 +- .../src/pages/app/CategoryDetailsPage.tsx | 9 ++++--- .../src/pages/app/FeedEntriesPage.tsx | 2 +- 14 files changed, 104 insertions(+), 19 deletions(-) diff --git a/commafeed-client/src/App.tsx b/commafeed-client/src/App.tsx index 7a626ce7..54f47abb 100644 --- a/commafeed-client/src/App.tsx +++ b/commafeed-client/src/App.tsx @@ -64,7 +64,7 @@ function Providers(props: { children: React.ReactNode }) { function AppRoutes() { return ( - } /> + } /> } /> } /> } /> diff --git a/commafeed-client/src/app/client.ts b/commafeed-client/src/app/client.ts index 0504b026..2f21f5ac 100644 --- a/commafeed-client/src/app/client.ts +++ b/commafeed-client/src/app/client.ts @@ -18,6 +18,7 @@ import { RegistrationRequest, ServerInfo, Settings, + StarRequest, SubscribeRequest, Subscription, UserModel, @@ -44,6 +45,7 @@ export const client = { }, entry: { mark: (req: MarkRequest) => axiosInstance.post("entry/mark", req), + star: (req: StarRequest) => axiosInstance.post("entry/star", req), }, feed: { get: (id: string) => axiosInstance.get(`feed/get/${id}`), diff --git a/commafeed-client/src/app/constants.ts b/commafeed-client/src/app/constants.ts index 5b335b40..340d51d9 100644 --- a/commafeed-client/src/app/constants.ts +++ b/commafeed-client/src/app/constants.ts @@ -1,9 +1,27 @@ +import { t } from "@lingui/macro" import { DEFAULT_THEME } from "@mantine/core" +import { Category } from "./types" -export const Constants = { - categoryIds: { - all: "all", +const categories: { [key: string]: Category } = { + all: { + id: "all", + name: t`All`, + expanded: false, + children: [], + feeds: [], + position: 0, }, + starred: { + id: "starred", + name: t`Starred`, + expanded: false, + children: [], + feeds: [], + position: 1, + }, +} +export const Constants = { + categories, layout: { mobileBreakpoint: DEFAULT_THEME.breakpoints.md, headerHeight: 60, diff --git a/commafeed-client/src/app/slices/entries.ts b/commafeed-client/src/app/slices/entries.ts index 8d3a9d9a..04d481af 100644 --- a/commafeed-client/src/app/slices/entries.ts +++ b/commafeed-client/src/app/slices/entries.ts @@ -28,7 +28,7 @@ interface EntriesState { const initialState: EntriesState = { source: { type: "category", - id: Constants.categoryIds.all, + id: Constants.categories.all.id, }, sourceLabel: "", sourceWebsiteUrl: "", @@ -87,6 +87,13 @@ export const markAllEntries = createAsyncThunk { + client.entry.star({ + id: arg.entry.id, + feedId: +arg.entry.feedId, + starred: arg.starred, + }) +}) export const selectEntry = createAsyncThunk("entries/entry/select", (arg, thunkApi) => { const state = thunkApi.getState() const entry = state.entries.entries.find(e => e.id === arg.id) @@ -159,6 +166,13 @@ export const entriesSlice = createSlice({ e.read = true }) }) + builder.addCase(starEntry.pending, (state, action) => { + state.entries + .filter(e => action.meta.arg.entry.id === e.id && action.meta.arg.entry.feedId === e.feedId) + .forEach(e => { + e.starred = action.meta.arg.starred + }) + }) builder.addCase(loadEntries.pending, (state, action) => { state.source = action.meta.arg state.entries = [] diff --git a/commafeed-client/src/app/slices/redirect.ts b/commafeed-client/src/app/slices/redirect.ts index 32252bce..81b8bc6c 100644 --- a/commafeed-client/src/app/slices/redirect.ts +++ b/commafeed-client/src/app/slices/redirect.ts @@ -21,7 +21,7 @@ export const redirectToCategory = createAsyncThunk("redirect/category", (id: str thunkApi.dispatch(redirectTo(`/app/category/${id}`)) ) export const redirectToRootCategory = createAsyncThunk("redirect/category/root", (_, thunkApi) => - thunkApi.dispatch(redirectToCategory(Constants.categoryIds.all)) + thunkApi.dispatch(redirectToCategory(Constants.categories.all.id)) ) export const redirectToCategoryDetails = createAsyncThunk("redirect/category/details", (id: string, thunkApi) => thunkApi.dispatch(redirectTo(`/app/category/${id}/details`)) diff --git a/commafeed-client/src/components/content/FeedEntryFooter.tsx b/commafeed-client/src/components/content/FeedEntryFooter.tsx index 79ba3a26..79ae2c85 100644 --- a/commafeed-client/src/components/content/FeedEntryFooter.tsx +++ b/commafeed-client/src/components/content/FeedEntryFooter.tsx @@ -1,10 +1,10 @@ import { t } from "@lingui/macro" import { Checkbox, Group } from "@mantine/core" -import { markEntry } from "app/slices/entries" +import { markEntry, starEntry } from "app/slices/entries" import { useAppDispatch } from "app/store" import { Entry } from "app/types" import { ActionButton } from "components/ActionButtton" -import { TbExternalLink } from "react-icons/tb" +import { TbExternalLink, TbStar, TbStarOff } from "react-icons/tb" interface FeedEntryFooterProps { entry: Entry @@ -27,6 +27,11 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) { }} /> )} + : } + label={props.entry.starred ? t`Unstar` : t`Star`} + onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))} + /> } label={t`Open link`} /> diff --git a/commafeed-client/src/components/content/add/CategorySelect.tsx b/commafeed-client/src/components/content/add/CategorySelect.tsx index 562d58a3..39dcbdd6 100644 --- a/commafeed-client/src/components/content/add/CategorySelect.tsx +++ b/commafeed-client/src/components/content/add/CategorySelect.tsx @@ -10,7 +10,7 @@ export function CategorySelect(props: CategorySelectProps) { const rootCategory = useAppSelector(state => state.tree.rootCategory) const categories = rootCategory && flattenCategoryTree(rootCategory) const selectData: SelectItem[] | undefined = categories - ?.filter(c => c.id !== Constants.categoryIds.all) + ?.filter(c => c.id !== Constants.categories.all.id) .sort((c1, c2) => c1.name.localeCompare(c2.name)) .map(c => ({ label: c.name, @@ -19,7 +19,7 @@ export function CategorySelect(props: CategorySelectProps) { if (props.withAll) { selectData?.unshift({ label: t`All`, - value: Constants.categoryIds.all, + value: Constants.categories.all.id, }) } diff --git a/commafeed-client/src/components/content/add/Subscribe.tsx b/commafeed-client/src/components/content/add/Subscribe.tsx index 61c60bb7..8e93afb1 100644 --- a/commafeed-client/src/components/content/add/Subscribe.tsx +++ b/commafeed-client/src/components/content/add/Subscribe.tsx @@ -27,7 +27,7 @@ export function Subscribe() { initialValues: { url: "", title: "", - categoryId: Constants.categoryIds.all, + categoryId: Constants.categories.all.id, }, }) diff --git a/commafeed-client/src/components/sidebar/Tree.tsx b/commafeed-client/src/components/sidebar/Tree.tsx index d42b0a20..926d80c6 100644 --- a/commafeed-client/src/components/sidebar/Tree.tsx +++ b/commafeed-client/src/components/sidebar/Tree.tsx @@ -9,11 +9,12 @@ import { categoryUnreadCount, flattenCategoryTree } from "app/utils" import { Loader } from "components/Loader" import { OnDesktop } from "components/responsive/OnDesktop" import React from "react" -import { FaChevronDown, FaChevronRight, FaInbox } from "react-icons/fa" +import { FaChevronDown, FaChevronRight, FaInbox, FaStar } from "react-icons/fa" import { TreeNode } from "./TreeNode" import { TreeSearch } from "./TreeSearch" const allIcon = +const starredIcon = const expandedIcon = const collapsedIcon = @@ -47,11 +48,24 @@ export function Tree() { const allCategoryNode = () => ( + ) + const starredCategoryNode = () => ( + {allCategoryNode()} + {starredCategoryNode()} {root.children.map(c => recursiveCategoryNode(c))} {root.feeds.map(f => feedNode(f))} diff --git a/commafeed-client/src/locales/en/messages.po b/commafeed-client/src/locales/en/messages.po index 1db276ad..413bc9ec 100644 --- a/commafeed-client/src/locales/en/messages.po +++ b/commafeed-client/src/locales/en/messages.po @@ -56,6 +56,7 @@ msgstr "Add user" msgid "Admin" msgstr "Admin" +#: src/app/constants.ts #: src/components/content/add/CategorySelect.tsx #: src/components/header/Header.tsx #: src/components/sidebar/Tree.tsx @@ -586,6 +587,15 @@ msgstr "Something bad just happened..." msgid "Space" msgstr "Space" +#: src/components/content/FeedEntryFooter.tsx +msgid "Star" +msgstr "Star" + +#: src/app/constants.ts +#: src/components/sidebar/Tree.tsx +msgid "Starred" +msgstr "Starred" + #: src/components/content/add/Subscribe.tsx #: src/components/content/add/Subscribe.tsx #: src/pages/app/AddPage.tsx @@ -624,6 +634,10 @@ msgstr "Try out CommaFeed with the demo account: demo/demo" msgid "Unread" msgstr "Unread" +#: src/components/content/FeedEntryFooter.tsx +msgid "Unstar" +msgstr "Unstar" + #: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx msgid "Unsubscribe" diff --git a/commafeed-client/src/locales/fr/messages.po b/commafeed-client/src/locales/fr/messages.po index b8d79e13..53a6adeb 100644 --- a/commafeed-client/src/locales/fr/messages.po +++ b/commafeed-client/src/locales/fr/messages.po @@ -56,6 +56,7 @@ msgstr "Ajouter un utilisateur" msgid "Admin" msgstr "Administrateur" +#: src/app/constants.ts #: src/components/content/add/CategorySelect.tsx #: src/components/header/Header.tsx #: src/components/sidebar/Tree.tsx @@ -586,6 +587,15 @@ msgstr "Quelque chose s'est mal passé..." msgid "Space" msgstr "Espace" +#: src/components/content/FeedEntryFooter.tsx +msgid "Star" +msgstr "Ajouter aux favoris" + +#: src/app/constants.ts +#: src/components/sidebar/Tree.tsx +msgid "Starred" +msgstr "Favoris" + #: src/components/content/add/Subscribe.tsx #: src/components/content/add/Subscribe.tsx #: src/pages/app/AddPage.tsx @@ -624,6 +634,10 @@ msgstr "Essayez CommaFeed avec le compte de démonstration : demo/demo" msgid "Unread" msgstr "Non lu" +#: src/components/content/FeedEntryFooter.tsx +msgid "Unstar" +msgstr "Retirer des favoris" + #: src/pages/app/FeedDetailsPage.tsx #: src/pages/app/FeedDetailsPage.tsx msgid "Unsubscribe" diff --git a/commafeed-client/src/pages/app/AboutPage.tsx b/commafeed-client/src/pages/app/AboutPage.tsx index 6b5ce96c..c0ea7a5a 100644 --- a/commafeed-client/src/pages/app/AboutPage.tsx +++ b/commafeed-client/src/pages/app/AboutPage.tsx @@ -31,7 +31,7 @@ function Section(props: { title: string; icon: React.ReactNode; children: React. } function NextUnreadBookmarklet() { - const [categoryId, setCategoryId] = useState(Constants.categoryIds.all) + const [categoryId, setCategoryId] = useState(Constants.categories.all.id) const [order, setOrder] = useState("desc") const baseUrl = window.location.href.substring(0, window.location.href.lastIndexOf("#")) const href = `javascript:window.location.href='${baseUrl}next?category=${categoryId}&order=${order}&t='+new Date().getTime();` diff --git a/commafeed-client/src/pages/app/CategoryDetailsPage.tsx b/commafeed-client/src/pages/app/CategoryDetailsPage.tsx index 0cd68bef..86904f8a 100644 --- a/commafeed-client/src/pages/app/CategoryDetailsPage.tsx +++ b/commafeed-client/src/pages/app/CategoryDetailsPage.tsx @@ -18,13 +18,16 @@ import { TbDeviceFloppy, TbTrash } from "react-icons/tb" import { useParams } from "react-router-dom" export function CategoryDetailsPage() { - const { id = Constants.categoryIds.all } = useParams() + const { id = Constants.categories.all.id } = useParams() const apiKey = useAppSelector(state => state.user.profile?.apiKey) const dispatch = useAppDispatch() const query = useAsync(() => client.category.getRoot(), []) - const category = query.result && flattenCategoryTree(query.result.data).find(c => c.id === id) + const category = + id === Constants.categories.starred.id + ? Constants.categories.starred + : query.result && flattenCategoryTree(query.result.data).find(c => c.id === id) const form = useForm() const { setValues } = form @@ -69,7 +72,7 @@ export function CategoryDetailsPage() { }) }, [setValues, category]) - const editable = id !== Constants.categoryIds.all + const editable = id !== Constants.categories.all.id && id !== Constants.categories.starred.id if (!category) return return ( diff --git a/commafeed-client/src/pages/app/FeedEntriesPage.tsx b/commafeed-client/src/pages/app/FeedEntriesPage.tsx index 9c0d3066..4b9e2ee9 100644 --- a/commafeed-client/src/pages/app/FeedEntriesPage.tsx +++ b/commafeed-client/src/pages/app/FeedEntriesPage.tsx @@ -29,7 +29,7 @@ interface FeedEntriesPageProps { export function FeedEntriesPage(props: FeedEntriesPageProps) { const location = useLocation() - const { id = Constants.categoryIds.all } = useParams() + const { id = Constants.categories.all.id } = useParams() const viewport = useViewportSize() const theme = useMantineTheme() const rootCategory = useAppSelector(state => state.tree.rootCategory)