mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
split thunks from slices to avoid circular dependencies
This commit is contained in:
@@ -5,8 +5,8 @@ import { useColorScheme } from "@mantine/hooks"
|
|||||||
import { ModalsProvider } from "@mantine/modals"
|
import { ModalsProvider } from "@mantine/modals"
|
||||||
import { Notifications } from "@mantine/notifications"
|
import { Notifications } from "@mantine/notifications"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectTo } from "app/slices/redirect"
|
import { redirectTo } from "app/redirect/slice"
|
||||||
import { reloadServerInfos } from "app/slices/server"
|
import { reloadServerInfos } from "app/server/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { categoryUnreadCount } from "app/utils"
|
import { categoryUnreadCount } from "app/utils"
|
||||||
import { ErrorBoundary } from "components/ErrorBoundary"
|
import { ErrorBoundary } from "components/ErrorBoundary"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/* eslint-disable import/first */
|
/* eslint-disable import/first */
|
||||||
import { configureStore } from "@reduxjs/toolkit"
|
import { configureStore } from "@reduxjs/toolkit"
|
||||||
import { type client } from "app/client"
|
import { type client } from "app/client"
|
||||||
|
import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "app/entries/thunks"
|
||||||
import { reducers } from "app/store"
|
import { reducers } from "app/store"
|
||||||
import { type Entries, type Entry } from "app/types"
|
import { type Entries, type Entry } from "app/types"
|
||||||
import { type AxiosResponse } from "axios"
|
import { type AxiosResponse } from "axios"
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||||
import { mockReset } from "vitest-mock-extended"
|
import { mockReset } from "vitest-mock-extended"
|
||||||
import { loadEntries, loadMoreEntries, markAllEntries, markEntry } from "./entries"
|
|
||||||
|
|
||||||
const mockClient = await vi.hoisted(async () => {
|
const mockClient = await vi.hoisted(async () => {
|
||||||
const mockModule = await import("vitest-mock-extended")
|
const mockModule = await import("vitest-mock-extended")
|
||||||
134
commafeed-client/src/app/entries/slice.ts
Normal file
134
commafeed-client/src/app/entries/slice.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||||
|
import { Constants } from "app/constants"
|
||||||
|
import { loadEntries, loadMoreEntries, markAllEntries, markEntry, markMultipleEntries, starEntry, tagEntry } from "app/entries/thunks"
|
||||||
|
import { type Entry } from "app/types"
|
||||||
|
|
||||||
|
export type EntrySourceType = "category" | "feed" | "tag"
|
||||||
|
|
||||||
|
export interface EntrySource {
|
||||||
|
type: EntrySourceType
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExpendableEntry = Entry & { expanded?: boolean }
|
||||||
|
|
||||||
|
interface EntriesState {
|
||||||
|
/** selected source */
|
||||||
|
source: EntrySource
|
||||||
|
sourceLabel: string
|
||||||
|
sourceWebsiteUrl: string
|
||||||
|
entries: ExpendableEntry[]
|
||||||
|
/** stores when the first batch of entries were retrieved
|
||||||
|
*
|
||||||
|
* this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown
|
||||||
|
*/
|
||||||
|
timestamp?: number
|
||||||
|
selectedEntryId?: string
|
||||||
|
hasMore: boolean
|
||||||
|
loading: boolean
|
||||||
|
search?: string
|
||||||
|
scrollingToEntry: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: EntriesState = {
|
||||||
|
source: {
|
||||||
|
type: "category",
|
||||||
|
id: Constants.categories.all.id,
|
||||||
|
},
|
||||||
|
sourceLabel: "",
|
||||||
|
sourceWebsiteUrl: "",
|
||||||
|
entries: [],
|
||||||
|
hasMore: true,
|
||||||
|
loading: false,
|
||||||
|
scrollingToEntry: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entriesSlice = createSlice({
|
||||||
|
name: "entries",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSelectedEntry: (state, action: PayloadAction<Entry>) => {
|
||||||
|
state.selectedEntryId = action.payload.id
|
||||||
|
},
|
||||||
|
setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => {
|
||||||
|
state.entries
|
||||||
|
.filter(e => e.id === action.payload.entry.id)
|
||||||
|
.forEach(e => {
|
||||||
|
e.expanded = action.payload.expanded
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setScrollingToEntry: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.scrollingToEntry = action.payload
|
||||||
|
},
|
||||||
|
setSearch: (state, action: PayloadAction<string>) => {
|
||||||
|
state.search = action.payload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder.addCase(markEntry.pending, (state, action) => {
|
||||||
|
state.entries
|
||||||
|
.filter(e => e.id === action.meta.arg.entry.id)
|
||||||
|
.forEach(e => {
|
||||||
|
e.read = action.meta.arg.read
|
||||||
|
})
|
||||||
|
})
|
||||||
|
builder.addCase(markMultipleEntries.pending, (state, action) => {
|
||||||
|
state.entries
|
||||||
|
.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))
|
||||||
|
.forEach(e => {
|
||||||
|
e.read = action.meta.arg.read
|
||||||
|
})
|
||||||
|
})
|
||||||
|
builder.addCase(markAllEntries.pending, (state, action) => {
|
||||||
|
state.entries
|
||||||
|
.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true))
|
||||||
|
.forEach(e => {
|
||||||
|
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.source
|
||||||
|
state.entries = []
|
||||||
|
state.timestamp = undefined
|
||||||
|
state.sourceLabel = ""
|
||||||
|
state.sourceWebsiteUrl = ""
|
||||||
|
state.hasMore = true
|
||||||
|
state.selectedEntryId = undefined
|
||||||
|
state.loading = true
|
||||||
|
})
|
||||||
|
builder.addCase(loadMoreEntries.pending, state => {
|
||||||
|
state.loading = true
|
||||||
|
})
|
||||||
|
builder.addCase(loadEntries.fulfilled, (state, action) => {
|
||||||
|
state.entries = action.payload.entries
|
||||||
|
state.timestamp = action.payload.timestamp
|
||||||
|
state.sourceLabel = action.payload.name
|
||||||
|
state.sourceWebsiteUrl = action.payload.feedLink
|
||||||
|
state.hasMore = action.payload.hasMore
|
||||||
|
state.loading = false
|
||||||
|
})
|
||||||
|
builder.addCase(loadMoreEntries.fulfilled, (state, action) => {
|
||||||
|
// remove already existing entries
|
||||||
|
const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id))
|
||||||
|
state.entries = [...state.entries, ...entriesToAdd]
|
||||||
|
state.hasMore = action.payload.hasMore
|
||||||
|
state.loading = false
|
||||||
|
})
|
||||||
|
builder.addCase(tagEntry.pending, (state, action) => {
|
||||||
|
state.entries
|
||||||
|
.filter(e => +e.id === action.meta.arg.entryId)
|
||||||
|
.forEach(e => {
|
||||||
|
e.tags = action.meta.arg.tags
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setSearch } = entriesSlice.actions
|
||||||
@@ -1,55 +1,13 @@
|
|||||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { type RootState } from "app/store"
|
import { entriesSlice, type EntrySource, type EntrySourceType, setSearch } from "app/entries/slice"
|
||||||
import { createAppAsyncThunk } from "app/thunk"
|
import type { RootState } from "app/store"
|
||||||
import { type Entry, type MarkRequest, type TagRequest } from "app/types"
|
import { reloadTree } from "app/tree/thunks"
|
||||||
|
import type { Entry, MarkRequest, TagRequest } from "app/types"
|
||||||
|
import { reloadTags } from "app/user/thunks"
|
||||||
import { scrollToWithCallback } from "app/utils"
|
import { scrollToWithCallback } from "app/utils"
|
||||||
import { flushSync } from "react-dom"
|
import { flushSync } from "react-dom"
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import { reloadTree } from "./tree"
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import { reloadTags } from "./user"
|
|
||||||
|
|
||||||
export type EntrySourceType = "category" | "feed" | "tag"
|
|
||||||
|
|
||||||
export interface EntrySource {
|
|
||||||
type: EntrySourceType
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ExpendableEntry = Entry & { expanded?: boolean }
|
|
||||||
|
|
||||||
interface EntriesState {
|
|
||||||
/** selected source */
|
|
||||||
source: EntrySource
|
|
||||||
sourceLabel: string
|
|
||||||
sourceWebsiteUrl: string
|
|
||||||
entries: ExpendableEntry[]
|
|
||||||
/** stores when the first batch of entries were retrieved
|
|
||||||
*
|
|
||||||
* this is used when marking all entries of a feed/category to only mark entries up to that timestamp as newer entries were potentially never shown
|
|
||||||
*/
|
|
||||||
timestamp?: number
|
|
||||||
selectedEntryId?: string
|
|
||||||
hasMore: boolean
|
|
||||||
loading: boolean
|
|
||||||
search?: string
|
|
||||||
scrollingToEntry: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: EntriesState = {
|
|
||||||
source: {
|
|
||||||
type: "category",
|
|
||||||
id: Constants.categories.all.id,
|
|
||||||
},
|
|
||||||
sourceLabel: "",
|
|
||||||
sourceWebsiteUrl: "",
|
|
||||||
entries: [],
|
|
||||||
hasMore: true,
|
|
||||||
loading: false,
|
|
||||||
scrollingToEntry: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const getEndpoint = (sourceType: EntrySourceType) =>
|
const getEndpoint = (sourceType: EntrySourceType) =>
|
||||||
sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries
|
sourceType === "category" || sourceType === "tag" ? client.category.getEntries : client.feed.getEntries
|
||||||
@@ -280,94 +238,3 @@ export const tagEntry = createAppAsyncThunk("entries/entry/tag", async (arg: Tag
|
|||||||
await client.entry.tag(arg)
|
await client.entry.tag(arg)
|
||||||
thunkApi.dispatch(reloadTags())
|
thunkApi.dispatch(reloadTags())
|
||||||
})
|
})
|
||||||
|
|
||||||
export const entriesSlice = createSlice({
|
|
||||||
name: "entries",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
setSelectedEntry: (state, action: PayloadAction<Entry>) => {
|
|
||||||
state.selectedEntryId = action.payload.id
|
|
||||||
},
|
|
||||||
setEntryExpanded: (state, action: PayloadAction<{ entry: Entry; expanded: boolean }>) => {
|
|
||||||
state.entries
|
|
||||||
.filter(e => e.id === action.payload.entry.id)
|
|
||||||
.forEach(e => {
|
|
||||||
e.expanded = action.payload.expanded
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setScrollingToEntry: (state, action: PayloadAction<boolean>) => {
|
|
||||||
state.scrollingToEntry = action.payload
|
|
||||||
},
|
|
||||||
setSearch: (state, action: PayloadAction<string>) => {
|
|
||||||
state.search = action.payload
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extraReducers: builder => {
|
|
||||||
builder.addCase(markEntry.pending, (state, action) => {
|
|
||||||
state.entries
|
|
||||||
.filter(e => e.id === action.meta.arg.entry.id)
|
|
||||||
.forEach(e => {
|
|
||||||
e.read = action.meta.arg.read
|
|
||||||
})
|
|
||||||
})
|
|
||||||
builder.addCase(markMultipleEntries.pending, (state, action) => {
|
|
||||||
state.entries
|
|
||||||
.filter(e => action.meta.arg.entries.some(e2 => e2.id === e.id))
|
|
||||||
.forEach(e => {
|
|
||||||
e.read = action.meta.arg.read
|
|
||||||
})
|
|
||||||
})
|
|
||||||
builder.addCase(markAllEntries.pending, (state, action) => {
|
|
||||||
state.entries
|
|
||||||
.filter(e => (action.meta.arg.req.olderThan ? e.date < action.meta.arg.req.olderThan : true))
|
|
||||||
.forEach(e => {
|
|
||||||
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.source
|
|
||||||
state.entries = []
|
|
||||||
state.timestamp = undefined
|
|
||||||
state.sourceLabel = ""
|
|
||||||
state.sourceWebsiteUrl = ""
|
|
||||||
state.hasMore = true
|
|
||||||
state.selectedEntryId = undefined
|
|
||||||
state.loading = true
|
|
||||||
})
|
|
||||||
builder.addCase(loadMoreEntries.pending, state => {
|
|
||||||
state.loading = true
|
|
||||||
})
|
|
||||||
builder.addCase(loadEntries.fulfilled, (state, action) => {
|
|
||||||
state.entries = action.payload.entries
|
|
||||||
state.timestamp = action.payload.timestamp
|
|
||||||
state.sourceLabel = action.payload.name
|
|
||||||
state.sourceWebsiteUrl = action.payload.feedLink
|
|
||||||
state.hasMore = action.payload.hasMore
|
|
||||||
state.loading = false
|
|
||||||
})
|
|
||||||
builder.addCase(loadMoreEntries.fulfilled, (state, action) => {
|
|
||||||
// remove already existing entries
|
|
||||||
const entriesToAdd = action.payload.entries.filter(e => !state.entries.some(e2 => e.id === e2.id))
|
|
||||||
state.entries = [...state.entries, ...entriesToAdd]
|
|
||||||
state.hasMore = action.payload.hasMore
|
|
||||||
state.loading = false
|
|
||||||
})
|
|
||||||
builder.addCase(tagEntry.pending, (state, action) => {
|
|
||||||
state.entries
|
|
||||||
.filter(e => +e.id === action.meta.arg.entryId)
|
|
||||||
.forEach(e => {
|
|
||||||
e.tags = action.meta.arg.tags
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { setSearch } = entriesSlice.actions
|
|
||||||
export default entriesSlice.reducer
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { redirectToCategory } from "app/redirect/thunks"
|
||||||
import { store } from "app/store"
|
import { store } from "app/store"
|
||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
import { redirectToCategory } from "./redirect"
|
|
||||||
|
|
||||||
describe("redirects", () => {
|
describe("redirects", () => {
|
||||||
it("redirects to category", async () => {
|
it("redirects to category", async () => {
|
||||||
19
commafeed-client/src/app/redirect/slice.ts
Normal file
19
commafeed-client/src/app/redirect/slice.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
interface RedirectState {
|
||||||
|
to?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: RedirectState = {}
|
||||||
|
|
||||||
|
export const redirectSlice = createSlice({
|
||||||
|
name: "redirect",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
redirectTo: (state, action: PayloadAction<string | undefined>) => {
|
||||||
|
state.to = action.payload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { redirectTo } = redirectSlice.actions
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { createAppAsyncThunk } from "app/thunk"
|
import { redirectTo } from "app/redirect/slice"
|
||||||
|
|
||||||
interface RedirectState {
|
|
||||||
to?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: RedirectState = {}
|
|
||||||
|
|
||||||
export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login")))
|
export const redirectToLogin = createAppAsyncThunk("redirect/login", (_, thunkApi) => thunkApi.dispatch(redirectTo("/login")))
|
||||||
export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register")))
|
export const redirectToRegistration = createAppAsyncThunk("redirect/register", (_, thunkApi) => thunkApi.dispatch(redirectTo("/register")))
|
||||||
@@ -49,16 +43,3 @@ export const redirectToMetrics = createAppAsyncThunk("redirect/admin/metrics", (
|
|||||||
)
|
)
|
||||||
export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate")))
|
export const redirectToDonate = createAppAsyncThunk("redirect/donate", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/donate")))
|
||||||
export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about")))
|
export const redirectToAbout = createAppAsyncThunk("redirect/about", (_, thunkApi) => thunkApi.dispatch(redirectTo("/app/about")))
|
||||||
|
|
||||||
export const redirectSlice = createSlice({
|
|
||||||
name: "redirect",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
redirectTo: (state, action: PayloadAction<string | undefined>) => {
|
|
||||||
state.to = action.payload
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { redirectTo } = redirectSlice.actions
|
|
||||||
export default redirectSlice.reducer
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||||
import { client } from "app/client"
|
import { reloadServerInfos } from "app/server/thunks"
|
||||||
import { createAppAsyncThunk } from "app/thunk"
|
|
||||||
import { type ServerInfo } from "app/types"
|
import { type ServerInfo } from "app/types"
|
||||||
|
|
||||||
interface ServerState {
|
interface ServerState {
|
||||||
@@ -12,7 +11,6 @@ const initialState: ServerState = {
|
|||||||
webSocketConnected: false,
|
webSocketConnected: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data))
|
|
||||||
export const serverSlice = createSlice({
|
export const serverSlice = createSlice({
|
||||||
name: "server",
|
name: "server",
|
||||||
initialState,
|
initialState,
|
||||||
@@ -29,4 +27,3 @@ export const serverSlice = createSlice({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const { setWebSocketConnected } = serverSlice.actions
|
export const { setWebSocketConnected } = serverSlice.actions
|
||||||
export default serverSlice.reducer
|
|
||||||
4
commafeed-client/src/app/server/thunks.ts
Normal file
4
commafeed-client/src/app/server/thunks.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
|
import { client } from "app/client"
|
||||||
|
|
||||||
|
export const reloadServerInfos = createAppAsyncThunk("server/infos", async () => await client.server.getServerInfos().then(r => r.data))
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { t } from "@lingui/macro"
|
|
||||||
import { showNotification } from "@mantine/notifications"
|
|
||||||
import { createSlice, isAnyOf } from "@reduxjs/toolkit"
|
|
||||||
import { client } from "app/client"
|
|
||||||
import { createAppAsyncThunk } from "app/thunk"
|
|
||||||
import { type ReadingMode, type ReadingOrder, type Settings, type SharingSettings, type UserModel } from "app/types"
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import { reloadEntries } from "./entries"
|
|
||||||
|
|
||||||
interface UserState {
|
|
||||||
settings?: Settings
|
|
||||||
profile?: UserModel
|
|
||||||
tags?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: UserState = {}
|
|
||||||
|
|
||||||
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
|
||||||
export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data))
|
|
||||||
export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data))
|
|
||||||
export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, readingMode })
|
|
||||||
thunkApi.dispatch(reloadEntries())
|
|
||||||
})
|
|
||||||
export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, readingOrder })
|
|
||||||
thunkApi.dispatch(reloadEntries())
|
|
||||||
})
|
|
||||||
export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, language })
|
|
||||||
})
|
|
||||||
export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
|
|
||||||
})
|
|
||||||
export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, showRead })
|
|
||||||
})
|
|
||||||
export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, scrollMarks })
|
|
||||||
})
|
|
||||||
export const changeAlwaysScrollToEntry = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, alwaysScrollToEntry })
|
|
||||||
})
|
|
||||||
export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
|
|
||||||
"settings/markAllAsReadConfirmation",
|
|
||||||
(markAllAsReadConfirmation: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({ ...settings, customContextMenu })
|
|
||||||
})
|
|
||||||
export const changeSharingSetting = createAppAsyncThunk(
|
|
||||||
"settings/sharingSetting",
|
|
||||||
(
|
|
||||||
sharingSetting: {
|
|
||||||
site: keyof SharingSettings
|
|
||||||
value: boolean
|
|
||||||
},
|
|
||||||
thunkApi
|
|
||||||
) => {
|
|
||||||
const { settings } = thunkApi.getState().user
|
|
||||||
if (!settings) return
|
|
||||||
client.user.saveSettings({
|
|
||||||
...settings,
|
|
||||||
sharingSettings: {
|
|
||||||
...settings.sharingSettings,
|
|
||||||
[sharingSetting.site]: sharingSetting.value,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export const userSlice = createSlice({
|
|
||||||
name: "user",
|
|
||||||
initialState,
|
|
||||||
reducers: {},
|
|
||||||
extraReducers: builder => {
|
|
||||||
builder.addCase(reloadSettings.fulfilled, (state, action) => {
|
|
||||||
state.settings = action.payload
|
|
||||||
})
|
|
||||||
builder.addCase(reloadProfile.fulfilled, (state, action) => {
|
|
||||||
state.profile = action.payload
|
|
||||||
})
|
|
||||||
builder.addCase(reloadTags.fulfilled, (state, action) => {
|
|
||||||
state.tags = action.payload
|
|
||||||
})
|
|
||||||
builder.addCase(changeReadingMode.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.readingMode = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeReadingOrder.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.readingOrder = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeLanguage.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.language = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeScrollSpeed.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.scrollSpeed = action.meta.arg ? 400 : 0
|
|
||||||
})
|
|
||||||
builder.addCase(changeShowRead.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.showRead = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeScrollMarks.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.scrollMarks = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.alwaysScrollToEntry = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.markAllAsReadConfirmation = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeCustomContextMenu.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.customContextMenu = action.meta.arg
|
|
||||||
})
|
|
||||||
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
|
||||||
if (!state.settings) return
|
|
||||||
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
|
||||||
})
|
|
||||||
builder.addMatcher(
|
|
||||||
isAnyOf(
|
|
||||||
changeLanguage.fulfilled,
|
|
||||||
changeScrollSpeed.fulfilled,
|
|
||||||
changeShowRead.fulfilled,
|
|
||||||
changeScrollMarks.fulfilled,
|
|
||||||
changeAlwaysScrollToEntry.fulfilled,
|
|
||||||
changeMarkAllAsReadConfirmation.fulfilled,
|
|
||||||
changeCustomContextMenu.fulfilled,
|
|
||||||
changeSharingSetting.fulfilled
|
|
||||||
),
|
|
||||||
() => {
|
|
||||||
showNotification({
|
|
||||||
message: t`Settings saved.`,
|
|
||||||
color: "green",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default userSlice.reducer
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit"
|
import { configureStore } from "@reduxjs/toolkit"
|
||||||
import { setupListeners } from "@reduxjs/toolkit/query"
|
import { setupListeners } from "@reduxjs/toolkit/query"
|
||||||
|
import { entriesSlice } from "app/entries/slice"
|
||||||
|
import { redirectSlice } from "app/redirect/slice"
|
||||||
|
import { serverSlice } from "app/server/slice"
|
||||||
|
import { treeSlice } from "app/tree/slice"
|
||||||
|
import { userSlice } from "app/user/slice"
|
||||||
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
import { type TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||||
import entriesReducer from "./slices/entries"
|
|
||||||
import redirectReducer from "./slices/redirect"
|
|
||||||
import serverReducer from "./slices/server"
|
|
||||||
import treeReducer from "./slices/tree"
|
|
||||||
import userReducer from "./slices/user"
|
|
||||||
|
|
||||||
export const reducers = {
|
export const reducers = {
|
||||||
entries: entriesReducer,
|
entries: entriesSlice.reducer,
|
||||||
redirect: redirectReducer,
|
redirect: redirectSlice.reducer,
|
||||||
tree: treeReducer,
|
tree: treeSlice.reducer,
|
||||||
server: serverReducer,
|
server: serverSlice.reducer,
|
||||||
user: userReducer,
|
user: userSlice.reducer,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = configureStore({ reducer: reducers })
|
export const store = configureStore({ reducer: reducers })
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
import { createSlice, type PayloadAction } from "@reduxjs/toolkit"
|
||||||
import { client } from "app/client"
|
import { markEntry } from "app/entries/thunks"
|
||||||
import { createAppAsyncThunk } from "app/thunk"
|
import { redirectTo } from "app/redirect/slice"
|
||||||
import { type Category, type CollapseRequest } from "app/types"
|
import { collapseTreeCategory, reloadTree } from "app/tree/thunks"
|
||||||
|
import { type Category } from "app/types"
|
||||||
import { visitCategoryTree } from "app/utils"
|
import { visitCategoryTree } from "app/utils"
|
||||||
// eslint-disable-next-line import/no-cycle
|
|
||||||
import { markEntry } from "./entries"
|
|
||||||
import { redirectTo } from "./redirect"
|
|
||||||
|
|
||||||
interface TreeState {
|
interface TreeState {
|
||||||
rootCategory?: Category
|
rootCategory?: Category
|
||||||
@@ -20,12 +18,6 @@ const initialState: TreeState = {
|
|||||||
sidebarVisible: true,
|
sidebarVisible: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data))
|
|
||||||
export const collapseTreeCategory = createAppAsyncThunk(
|
|
||||||
"tree/category/collapse",
|
|
||||||
async (req: CollapseRequest) => await client.category.collapse(req)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const treeSlice = createSlice({
|
export const treeSlice = createSlice({
|
||||||
name: "tree",
|
name: "tree",
|
||||||
initialState,
|
initialState,
|
||||||
@@ -67,4 +59,3 @@ export const treeSlice = createSlice({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const { setMobileMenuOpen, setSidebarWidth, toggleSidebar } = treeSlice.actions
|
export const { setMobileMenuOpen, setSidebarWidth, toggleSidebar } = treeSlice.actions
|
||||||
export default treeSlice.reducer
|
|
||||||
9
commafeed-client/src/app/tree/thunks.ts
Normal file
9
commafeed-client/src/app/tree/thunks.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
|
import { client } from "app/client"
|
||||||
|
import type { CollapseRequest } from "app/types"
|
||||||
|
|
||||||
|
export const reloadTree = createAppAsyncThunk("tree/reload", async () => await client.category.getRoot().then(r => r.data))
|
||||||
|
export const collapseTreeCategory = createAppAsyncThunk(
|
||||||
|
"tree/category/collapse",
|
||||||
|
async (req: CollapseRequest) => await client.category.collapse(req)
|
||||||
|
)
|
||||||
102
commafeed-client/src/app/user/slice.ts
Normal file
102
commafeed-client/src/app/user/slice.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { t } from "@lingui/macro"
|
||||||
|
import { showNotification } from "@mantine/notifications"
|
||||||
|
import { createSlice, isAnyOf } from "@reduxjs/toolkit"
|
||||||
|
import { type Settings, type UserModel } from "app/types"
|
||||||
|
import {
|
||||||
|
changeAlwaysScrollToEntry,
|
||||||
|
changeCustomContextMenu,
|
||||||
|
changeLanguage,
|
||||||
|
changeMarkAllAsReadConfirmation,
|
||||||
|
changeReadingMode,
|
||||||
|
changeReadingOrder,
|
||||||
|
changeScrollMarks,
|
||||||
|
changeScrollSpeed,
|
||||||
|
changeSharingSetting,
|
||||||
|
changeShowRead,
|
||||||
|
reloadProfile,
|
||||||
|
reloadSettings,
|
||||||
|
reloadTags,
|
||||||
|
} from "./thunks"
|
||||||
|
|
||||||
|
interface UserState {
|
||||||
|
settings?: Settings
|
||||||
|
profile?: UserModel
|
||||||
|
tags?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: UserState = {}
|
||||||
|
|
||||||
|
export const userSlice = createSlice({
|
||||||
|
name: "user",
|
||||||
|
initialState,
|
||||||
|
reducers: {},
|
||||||
|
extraReducers: builder => {
|
||||||
|
builder.addCase(reloadSettings.fulfilled, (state, action) => {
|
||||||
|
state.settings = action.payload
|
||||||
|
})
|
||||||
|
builder.addCase(reloadProfile.fulfilled, (state, action) => {
|
||||||
|
state.profile = action.payload
|
||||||
|
})
|
||||||
|
builder.addCase(reloadTags.fulfilled, (state, action) => {
|
||||||
|
state.tags = action.payload
|
||||||
|
})
|
||||||
|
builder.addCase(changeReadingMode.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.readingMode = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeReadingOrder.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.readingOrder = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeLanguage.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.language = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeScrollSpeed.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.scrollSpeed = action.meta.arg ? 400 : 0
|
||||||
|
})
|
||||||
|
builder.addCase(changeShowRead.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.showRead = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeScrollMarks.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.scrollMarks = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeAlwaysScrollToEntry.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.alwaysScrollToEntry = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeMarkAllAsReadConfirmation.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.markAllAsReadConfirmation = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeCustomContextMenu.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.customContextMenu = action.meta.arg
|
||||||
|
})
|
||||||
|
builder.addCase(changeSharingSetting.pending, (state, action) => {
|
||||||
|
if (!state.settings) return
|
||||||
|
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
|
||||||
|
})
|
||||||
|
builder.addMatcher(
|
||||||
|
isAnyOf(
|
||||||
|
changeLanguage.fulfilled,
|
||||||
|
changeScrollSpeed.fulfilled,
|
||||||
|
changeShowRead.fulfilled,
|
||||||
|
changeScrollMarks.fulfilled,
|
||||||
|
changeAlwaysScrollToEntry.fulfilled,
|
||||||
|
changeMarkAllAsReadConfirmation.fulfilled,
|
||||||
|
changeCustomContextMenu.fulfilled,
|
||||||
|
changeSharingSetting.fulfilled
|
||||||
|
),
|
||||||
|
() => {
|
||||||
|
showNotification({
|
||||||
|
message: t`Settings saved.`,
|
||||||
|
color: "green",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
78
commafeed-client/src/app/user/thunks.ts
Normal file
78
commafeed-client/src/app/user/thunks.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { createAppAsyncThunk } from "app/async-thunk"
|
||||||
|
import { client } from "app/client"
|
||||||
|
import { reloadEntries } from "app/entries/thunks"
|
||||||
|
import type { ReadingMode, ReadingOrder, SharingSettings } from "app/types"
|
||||||
|
|
||||||
|
export const reloadSettings = createAppAsyncThunk("settings/reload", async () => await client.user.getSettings().then(r => r.data))
|
||||||
|
export const reloadProfile = createAppAsyncThunk("profile/reload", async () => await client.user.getProfile().then(r => r.data))
|
||||||
|
export const reloadTags = createAppAsyncThunk("entries/tags", async () => await client.entry.getTags().then(r => r.data))
|
||||||
|
export const changeReadingMode = createAppAsyncThunk("settings/readingMode", (readingMode: ReadingMode, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, readingMode })
|
||||||
|
thunkApi.dispatch(reloadEntries())
|
||||||
|
})
|
||||||
|
export const changeReadingOrder = createAppAsyncThunk("settings/readingOrder", (readingOrder: ReadingOrder, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, readingOrder })
|
||||||
|
thunkApi.dispatch(reloadEntries())
|
||||||
|
})
|
||||||
|
export const changeLanguage = createAppAsyncThunk("settings/language", (language: string, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, language })
|
||||||
|
})
|
||||||
|
export const changeScrollSpeed = createAppAsyncThunk("settings/scrollSpeed", (speed: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
|
||||||
|
})
|
||||||
|
export const changeShowRead = createAppAsyncThunk("settings/showRead", (showRead: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, showRead })
|
||||||
|
})
|
||||||
|
export const changeScrollMarks = createAppAsyncThunk("settings/scrollMarks", (scrollMarks: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, scrollMarks })
|
||||||
|
})
|
||||||
|
export const changeAlwaysScrollToEntry = createAppAsyncThunk("settings/alwaysScrollToEntry", (alwaysScrollToEntry: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, alwaysScrollToEntry })
|
||||||
|
})
|
||||||
|
export const changeMarkAllAsReadConfirmation = createAppAsyncThunk(
|
||||||
|
"settings/markAllAsReadConfirmation",
|
||||||
|
(markAllAsReadConfirmation: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, markAllAsReadConfirmation })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
export const changeCustomContextMenu = createAppAsyncThunk("settings/customContextMenu", (customContextMenu: boolean, thunkApi) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({ ...settings, customContextMenu })
|
||||||
|
})
|
||||||
|
export const changeSharingSetting = createAppAsyncThunk(
|
||||||
|
"settings/sharingSetting",
|
||||||
|
(
|
||||||
|
sharingSetting: {
|
||||||
|
site: keyof SharingSettings
|
||||||
|
value: boolean
|
||||||
|
},
|
||||||
|
thunkApi
|
||||||
|
) => {
|
||||||
|
const { settings } = thunkApi.getState().user
|
||||||
|
if (!settings) return
|
||||||
|
client.user.saveSettings({
|
||||||
|
...settings,
|
||||||
|
sharingSettings: {
|
||||||
|
...settings.sharingSettings,
|
||||||
|
[sharingSetting.site]: sharingSetting.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -77,7 +77,6 @@ class HighlightMatcher extends Matcher {
|
|||||||
return <Mark>{children}</Mark>
|
return <Mark>{children}</Mark>
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
asTag(): string {
|
asTag(): string {
|
||||||
return "span"
|
return "span"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { Trans } from "@lingui/macro"
|
|||||||
import { Box } from "@mantine/core"
|
import { Box } from "@mantine/core"
|
||||||
import { openModal } from "@mantine/modals"
|
import { openModal } from "@mantine/modals"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
|
import { type ExpendableEntry } from "app/entries/slice"
|
||||||
import {
|
import {
|
||||||
type ExpendableEntry,
|
|
||||||
loadMoreEntries,
|
loadMoreEntries,
|
||||||
markAllEntries,
|
markAllEntries,
|
||||||
markEntry,
|
markEntry,
|
||||||
@@ -12,10 +12,10 @@ import {
|
|||||||
selectNextEntry,
|
selectNextEntry,
|
||||||
selectPreviousEntry,
|
selectPreviousEntry,
|
||||||
starEntry,
|
starEntry,
|
||||||
} from "app/slices/entries"
|
} from "app/entries/thunks"
|
||||||
import { redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { toggleSidebar } from "app/slices/tree"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { toggleSidebar } from "app/tree/slice"
|
||||||
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
import { useBrowserExtension } from "hooks/useBrowserExtension"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { createStyles, Group } from "@mantine/core"
|
import { createStyles, Group } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { markEntriesUpToEntry, markEntry, starEntry } from "app/slices/entries"
|
import { markEntriesUpToEntry, markEntry, starEntry } from "app/entries/thunks"
|
||||||
import { redirectToFeed } from "app/slices/redirect"
|
import { redirectToFeed } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { truncate } from "app/utils"
|
import { truncate } from "app/utils"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { Group, Indicator, MultiSelect, Popover } from "@mantine/core"
|
import { Group, Indicator, MultiSelect, Popover } from "@mantine/core"
|
||||||
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/slices/entries"
|
import { markEntriesUpToEntry, markEntry, starEntry, tagEntry } from "app/entries/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type Entry } from "app/types"
|
import { type Entry } from "app/types"
|
||||||
import { ActionButton } from "components/ActionButton"
|
import { ActionButton } from "components/ActionButton"
|
||||||
@@ -22,7 +22,13 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
|
|||||||
|
|
||||||
const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v)
|
const showSharingButtons = sharingSettings && Object.values(sharingSettings).some(v => v)
|
||||||
|
|
||||||
const readStatusButtonClicked = async () => await dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))
|
const readStatusButtonClicked = async () =>
|
||||||
|
await dispatch(
|
||||||
|
markEntry({
|
||||||
|
entry: props.entry,
|
||||||
|
read: !props.entry.read,
|
||||||
|
})
|
||||||
|
)
|
||||||
const onTagsChange = async (values: string[]) =>
|
const onTagsChange = async (values: string[]) =>
|
||||||
await dispatch(
|
await dispatch(
|
||||||
tagEntry({
|
tagEntry({
|
||||||
@@ -44,7 +50,14 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />}
|
icon={props.entry.starred ? <TbStarOff size={18} /> : <TbStar size={18} />}
|
||||||
label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
|
label={props.entry.starred ? <Trans>Unstar</Trans> : <Trans>Star</Trans>}
|
||||||
onClick={async () => await dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}
|
onClick={async () =>
|
||||||
|
await dispatch(
|
||||||
|
starEntry({
|
||||||
|
entry: props.entry,
|
||||||
|
starred: !props.entry.starred,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showSharingButtons && (
|
{showSharingButtons && (
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { t, Trans } from "@lingui/macro"
|
|||||||
import { Box, Button, Group, Stack, TextInput } from "@mantine/core"
|
import { Box, Button, Group, Stack, TextInput } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { type AddCategoryRequest } from "app/types"
|
import { type AddCategoryRequest } from "app/types"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { useAsyncCallback } from "react-async-hook"
|
import { useAsyncCallback } from "react-async-hook"
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { t, Trans } from "@lingui/macro"
|
|||||||
import { Box, Button, FileInput, Group, Stack } from "@mantine/core"
|
import { Box, Button, FileInput, Group, Stack } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { useAsyncCallback } from "react-async-hook"
|
import { useAsyncCallback } from "react-async-hook"
|
||||||
import { TbFileImport } from "react-icons/tb"
|
import { TbFileImport } from "react-icons/tb"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToFeed, redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToFeed, redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { type FeedInfoRequest, type SubscribeRequest } from "app/types"
|
import { type FeedInfoRequest, type SubscribeRequest } from "app/types"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
@@ -46,8 +46,11 @@ export function Subscribe() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const previousStep = () => {
|
const previousStep = () => {
|
||||||
if (activeStep === 0) dispatch(redirectToSelectedSource())
|
if (activeStep === 0) {
|
||||||
else setActiveStep(activeStep - 1)
|
dispatch(redirectToSelectedSource())
|
||||||
|
} else {
|
||||||
|
setActiveStep(activeStep - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const nextStep = (e: React.FormEvent<HTMLFormElement>) => {
|
const nextStep = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
if (activeStep === 0) {
|
if (activeStep === 0) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { ActionIcon, Box, Center, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
|
import { ActionIcon, Box, Center, Divider, Group, Indicator, Popover, TextInput } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/slices/entries"
|
import { reloadEntries, search, selectNextEntry, selectPreviousEntry } from "app/entries/thunks"
|
||||||
import { changeReadingMode, changeReadingOrder } from "app/slices/user"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { changeReadingMode, changeReadingOrder } from "app/user/thunks"
|
||||||
import { ActionButton } from "components/ActionButton"
|
import { ActionButton } from "components/ActionButton"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { useActionButton } from "hooks/useActionButton"
|
import { useActionButton } from "hooks/useActionButton"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
|
|
||||||
import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core"
|
import { Button, Code, Group, Modal, Slider, Stack, Text } from "@mantine/core"
|
||||||
import { markAllEntries } from "app/slices/entries"
|
import { markAllEntries } from "app/entries/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { ActionButton } from "components/ActionButton"
|
import { ActionButton } from "components/ActionButton"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
|
|||||||
import { Box, Divider, Group, Menu, SegmentedControl, type SegmentedControlItem, useMantineColorScheme } from "@mantine/core"
|
import { Box, Divider, Group, Menu, SegmentedControl, type SegmentedControlItem, useMantineColorScheme } from "@mantine/core"
|
||||||
import { showNotification } from "@mantine/notifications"
|
import { showNotification } from "@mantine/notifications"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/slices/redirect"
|
import { redirectToAbout, redirectToAdminUsers, redirectToDonate, redirectToMetrics, redirectToSettings } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type ViewMode } from "app/types"
|
import { type ViewMode } from "app/types"
|
||||||
import { useViewMode } from "hooks/useViewMode"
|
import { useViewMode } from "hooks/useViewMode"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
|
|||||||
import { Box, Button, Group, Stack } from "@mantine/core"
|
import { Box, Button, Group, Stack } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { CodeEditor } from "components/code/CodeEditor"
|
import { CodeEditor } from "components/code/CodeEditor"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
|
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { type SharingSettings } from "app/types"
|
||||||
import {
|
import {
|
||||||
changeAlwaysScrollToEntry,
|
changeAlwaysScrollToEntry,
|
||||||
changeCustomContextMenu,
|
changeCustomContextMenu,
|
||||||
@@ -10,9 +12,7 @@ import {
|
|||||||
changeScrollSpeed,
|
changeScrollSpeed,
|
||||||
changeSharingSetting,
|
changeSharingSetting,
|
||||||
changeShowRead,
|
changeShowRead,
|
||||||
} from "app/slices/user"
|
} from "app/user/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
|
||||||
import { type SharingSettings } from "app/types"
|
|
||||||
import { locales } from "i18n"
|
import { locales } from "i18n"
|
||||||
|
|
||||||
export function DisplaySettings() {
|
export function DisplaySettings() {
|
||||||
@@ -82,7 +82,14 @@ export function DisplaySettings() {
|
|||||||
key={site}
|
key={site}
|
||||||
label={Constants.sharing[site].label}
|
label={Constants.sharing[site].label}
|
||||||
checked={sharingSettings && sharingSettings[site]}
|
checked={sharingSettings && sharingSettings[site]}
|
||||||
onChange={async e => await dispatch(changeSharingSetting({ site, value: e.currentTarget.checked }))}
|
onChange={async e =>
|
||||||
|
await dispatch(
|
||||||
|
changeSharingSetting({
|
||||||
|
site,
|
||||||
|
value: e.currentTarget.checked,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, St
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { openConfirmModal } from "@mantine/modals"
|
import { openConfirmModal } from "@mantine/modals"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToLogin, redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToLogin, redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadProfile } from "app/slices/user"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type ProfileModificationRequest } from "app/types"
|
import { type ProfileModificationRequest } from "app/types"
|
||||||
|
import { reloadProfile } from "app/user/thunks"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useAsyncCallback } from "react-async-hook"
|
import { useAsyncCallback } from "react-async-hook"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import {
|
|||||||
redirectToFeedDetails,
|
redirectToFeedDetails,
|
||||||
redirectToTag,
|
redirectToTag,
|
||||||
redirectToTagDetails,
|
redirectToTagDetails,
|
||||||
} from "app/slices/redirect"
|
} from "app/redirect/thunks"
|
||||||
import { collapseTreeCategory } from "app/slices/tree"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { collapseTreeCategory } from "app/tree/thunks"
|
||||||
import { type Category, type Subscription } from "app/types"
|
import { type Category, type Subscription } from "app/types"
|
||||||
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
|
import { categoryUnreadCount, flattenCategoryTree } from "app/utils"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
@@ -36,8 +36,11 @@ export function Tree() {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const feedClicked = (e: React.MouseEvent, id: string) => {
|
const feedClicked = (e: React.MouseEvent, id: string) => {
|
||||||
if (e.detail === 2) dispatch(redirectToFeedDetails(id))
|
if (e.detail === 2) {
|
||||||
else dispatch(redirectToFeed(id))
|
dispatch(redirectToFeedDetails(id))
|
||||||
|
} else {
|
||||||
|
dispatch(redirectToFeed(id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const categoryClicked = (e: React.MouseEvent, id: string) => {
|
const categoryClicked = (e: React.MouseEvent, id: string) => {
|
||||||
if (e.detail === 2) {
|
if (e.detail === 2) {
|
||||||
@@ -57,8 +60,11 @@ export function Tree() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const tagClicked = (e: React.MouseEvent, id: string) => {
|
const tagClicked = (e: React.MouseEvent, id: string) => {
|
||||||
if (e.detail === 2) dispatch(redirectToTagDetails(id))
|
if (e.detail === 2) {
|
||||||
else dispatch(redirectToTag(id))
|
dispatch(redirectToTagDetails(id))
|
||||||
|
} else {
|
||||||
|
dispatch(redirectToTag(id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allCategoryNode = () => (
|
const allCategoryNode = () => (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { Box, Center, Kbd, TextInput } from "@mantine/core"
|
import { Box, Center, Kbd, TextInput } from "@mantine/core"
|
||||||
import { openSpotlight, type SpotlightAction, SpotlightProvider } from "@mantine/spotlight"
|
import { openSpotlight, type SpotlightAction, SpotlightProvider } from "@mantine/spotlight"
|
||||||
import { redirectToFeed } from "app/slices/redirect"
|
import { redirectToFeed } from "app/redirect/thunks"
|
||||||
import { useAppDispatch } from "app/store"
|
import { useAppDispatch } from "app/store"
|
||||||
import { type Subscription } from "app/types"
|
import { type Subscription } from "app/types"
|
||||||
import { FeedFavicon } from "components/content/FeedFavicon"
|
import { FeedFavicon } from "components/content/FeedFavicon"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { setWebSocketConnected } from "app/slices/server"
|
import { setWebSocketConnected } from "app/server/slice"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Trans } from "@lingui/macro"
|
import { Trans } from "@lingui/macro"
|
||||||
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core"
|
import { Anchor, Box, Center, Container, Divider, Group, Image, Title, useMantineColorScheme } from "@mantine/core"
|
||||||
import { client } from "app/client"
|
import { client } from "app/client"
|
||||||
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToApiDocumentation, redirectToLogin, redirectToRegistration, redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import welcome_page_dark from "assets/welcome_page_dark.png"
|
import welcome_page_dark from "assets/welcome_page_dark.png"
|
||||||
import welcome_page_light from "assets/welcome_page_light.png"
|
import welcome_page_light from "assets/welcome_page_light.png"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { t, Trans } from "@lingui/macro"
|
import { t, Trans } from "@lingui/macro"
|
||||||
import { Anchor, Box, Container, createStyles, List, NativeSelect, SimpleGrid, Title } from "@mantine/core"
|
import { Anchor, Box, Container, createStyles, List, NativeSelect, SimpleGrid, Title } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToApiDocumentation } from "app/slices/redirect"
|
import { redirectToApiDocumentation } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { CategorySelect } from "components/content/add/CategorySelect"
|
import { CategorySelect } from "components/content/add/CategorySelect"
|
||||||
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
import { KeyboardShortcutsHelp } from "components/KeyboardShortcutsHelp"
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useForm } from "@mantine/form"
|
|||||||
import { openConfirmModal } from "@mantine/modals"
|
import { openConfirmModal } from "@mantine/modals"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { type CategoryModificationRequest } from "app/types"
|
import { type CategoryModificationRequest } from "app/types"
|
||||||
import { flattenCategoryTree } from "app/utils"
|
import { flattenCategoryTree } from "app/utils"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInpu
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { openConfirmModal } from "@mantine/modals"
|
import { openConfirmModal } from "@mantine/modals"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToRootCategory, redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { reloadTree } from "app/slices/tree"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
import { type FeedModificationRequest } from "app/types"
|
import { type FeedModificationRequest } from "app/types"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
import { CategorySelect } from "components/content/add/CategorySelect"
|
import { CategorySelect } from "components/content/add/CategorySelect"
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { Trans } from "@lingui/macro"
|
|||||||
import { ActionIcon, Box, Center, createStyles, Divider, Group, Title, useMantineTheme } from "@mantine/core"
|
import { ActionIcon, Box, Center, createStyles, Divider, Group, Title, useMantineTheme } from "@mantine/core"
|
||||||
import { useViewportSize } from "@mantine/hooks"
|
import { useViewportSize } from "@mantine/hooks"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { type EntrySourceType, loadEntries } from "app/slices/entries"
|
import { type EntrySourceType } from "app/entries/slice"
|
||||||
import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "app/slices/redirect"
|
import { loadEntries } from "app/entries/thunks"
|
||||||
|
import { redirectToCategoryDetails, redirectToFeedDetails, redirectToTagDetails } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { flattenCategoryTree } from "app/utils"
|
import { flattenCategoryTree } from "app/utils"
|
||||||
import { FeedEntries } from "components/content/FeedEntries"
|
import { FeedEntries } from "components/content/FeedEntries"
|
||||||
@@ -47,9 +48,11 @@ export function FeedEntriesPage(props: FeedEntriesPageProps) {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const titleClicked = () => {
|
const titleClicked = () => {
|
||||||
if (props.sourceType === "category") dispatch(redirectToCategoryDetails(id))
|
if (props.sourceType === "category") {
|
||||||
else if (props.sourceType === "feed") dispatch(redirectToFeedDetails(id))
|
dispatch(redirectToCategoryDetails(id))
|
||||||
else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id))
|
} else if (props.sourceType === "feed") {
|
||||||
|
dispatch(redirectToFeedDetails(id))
|
||||||
|
} else if (props.sourceType === "tag") dispatch(redirectToTagDetails(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import {
|
|||||||
useMantineTheme,
|
useMantineTheme,
|
||||||
} from "@mantine/core"
|
} from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToAdd, redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { reloadTree, setMobileMenuOpen, setSidebarWidth } from "app/slices/tree"
|
|
||||||
import { reloadProfile, reloadSettings, reloadTags } from "app/slices/user"
|
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
|
import { setMobileMenuOpen, setSidebarWidth } from "app/tree/slice"
|
||||||
|
import { reloadTree } from "app/tree/thunks"
|
||||||
|
import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks"
|
||||||
import { AnnouncementDialog } from "components/AnnouncementDialog"
|
import { AnnouncementDialog } from "components/AnnouncementDialog"
|
||||||
import { Loader } from "components/Loader"
|
import { Loader } from "components/Loader"
|
||||||
import { Logo } from "components/Logo"
|
import { Logo } from "components/Logo"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Trans } from "@lingui/macro"
|
|||||||
|
|
||||||
import { Anchor, Box, Button, Container, Group, Input, Stack, Title } from "@mantine/core"
|
import { Anchor, Box, Button, Container, Group, Input, Stack, Title } from "@mantine/core"
|
||||||
import { Constants } from "app/constants"
|
import { Constants } from "app/constants"
|
||||||
import { redirectToSelectedSource } from "app/slices/redirect"
|
import { redirectToSelectedSource } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { useParams } from "react-router-dom"
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro"
|
|||||||
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
|
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type LoginRequest } from "app/types"
|
import { type LoginRequest } from "app/types"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro"
|
|||||||
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
|
import { Anchor, Box, Button, Center, Container, Group, Paper, PasswordInput, Stack, TextInput, Title } from "@mantine/core"
|
||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { client, errorToStrings } from "app/client"
|
import { client, errorToStrings } from "app/client"
|
||||||
import { redirectToRootCategory } from "app/slices/redirect"
|
import { redirectToRootCategory } from "app/redirect/thunks"
|
||||||
import { useAppDispatch, useAppSelector } from "app/store"
|
import { useAppDispatch, useAppSelector } from "app/store"
|
||||||
import { type RegistrationRequest } from "app/types"
|
import { type RegistrationRequest } from "app/types"
|
||||||
import { Alert } from "components/Alert"
|
import { Alert } from "components/Alert"
|
||||||
|
|||||||
Reference in New Issue
Block a user