- {listData.feeds?.slice(0, 5).map((feed) => (
-
-
- {feed.title}
-
-
- ))}
+ {listData.feedIds
+ ?.slice(0, 5)
+ .map((feedId) =>
)}
{"feedCount" in feed.data && (
{
@@ -117,3 +104,20 @@ export function Component() {
>
)
}
+
+const FeedRow = ({ feedId }: { feedId: string }) => {
+ const feed = useFeedById(feedId)
+ if (!feed) return null
+ return (
+
+
+ {feed.title}
+
+
+ )
+}
diff --git a/apps/renderer/src/services/feed.ts b/apps/renderer/src/services/feed.ts
index 1f4324098..4b014b2ec 100644
--- a/apps/renderer/src/services/feed.ts
+++ b/apps/renderer/src/services/feed.ts
@@ -1,23 +1,25 @@
+
import { browserDB } from "~/database"
-import type { TargetModel } from "~/models/types"
+import type { FeedOrListModel } from "~/models/types"
import { BaseService } from "./base"
import { CleanerService } from "./cleaner"
-type TargetModelWithId = TargetModel & { id: string }
+type TargetModelWithId = FeedOrListModel & { id: string }
class ServiceStatic extends BaseService
{
constructor() {
super(browserDB.feeds)
}
- override async upsertMany(data: TargetModel[]) {
+ override async upsertMany(data: FeedOrListModel[]) {
const filterData = data.filter((d) => d.id)
+
CleanerService.reset(filterData.map((d) => ({ type: "feed", id: d.id! })))
return this.table.bulkPut(filterData as TargetModelWithId[])
}
- override async upsert(data: TargetModel): Promise {
+ override async upsert(data: FeedOrListModel): Promise {
if (!data.id) return null
CleanerService.reset([{ type: "feed", id: data.id }])
return this.table.put(data as TargetModelWithId)
diff --git a/apps/renderer/src/store/entry/store.ts b/apps/renderer/src/store/entry/store.ts
index 86cc96730..36b267f80 100644
--- a/apps/renderer/src/store/entry/store.ts
+++ b/apps/renderer/src/store/entry/store.ts
@@ -5,7 +5,7 @@ import { isNil, merge, omit } from "lodash-es"
import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
import { getEntriesParams, omitObjectUndefinedValue } from "~/lib/utils"
-import type { CombinedEntryModel, EntryModel, TargetModel } from "~/models"
+import type { CombinedEntryModel, EntryModel, FeedOrListRespModel } from "~/models"
import { EntryService } from "~/services"
import { feedActions } from "../feed"
@@ -178,7 +178,7 @@ class EntryActions {
}
upsertMany(data: CombinedEntryModel[]) {
- const feeds = [] as TargetModel[]
+ const feeds = [] as FeedOrListRespModel[]
const entries = [] as EntryModel[]
const entry2Read = {} as Record
const entryFeedMap = {} as Record
diff --git a/apps/renderer/src/store/feed/hooks.ts b/apps/renderer/src/store/feed/hooks.ts
index ad28fea66..e0352b6a0 100644
--- a/apps/renderer/src/store/feed/hooks.ts
+++ b/apps/renderer/src/store/feed/hooks.ts
@@ -1,16 +1,19 @@
+import { useMutation } from "@tanstack/react-query"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
+import { toast } from "sonner"
import { useShallow } from "zustand/react/shallow"
import { FEED_COLLECTION_LIST, ROUTE_FEED_IN_FOLDER, ROUTE_FEED_PENDING, views } from "~/constants"
import { useRouteParams } from "~/hooks/biz/useRouteParams"
-import type { TargetModel } from "~/models"
+import { apiClient } from "~/lib/api-fetch"
+import type { FeedOrListRespModel } from "~/models"
import { getSubscriptionByFeedId } from "../subscription"
-import { useFeedStore } from "./store"
+import { feedActions, useFeedStore } from "./store"
import type { FeedQueryParams } from "./types"
-export const useFeedById = (feedId: Nullable): TargetModel | null =>
+export const useFeedById = (feedId: Nullable): FeedOrListRespModel | null =>
useFeedStore((state) => (feedId ? state.feeds[feedId] : null))
export const useFeedByIdOrUrl = (feed: FeedQueryParams) =>
@@ -26,7 +29,7 @@ export const useFeedByIdOrUrl = (feed: FeedQueryParams) =>
export const useFeedByIdSelector = (
feedId: Nullable,
- selector: (feed: TargetModel) => T,
+ selector: (feed: FeedOrListRespModel) => T,
) =>
useFeedStore(
useShallow((state) => (feedId && state.feeds[feedId] ? selector(state.feeds[feedId]) : null)),
@@ -45,7 +48,6 @@ export const useFeedHeaderTitle = () => {
switch (currentFeedId) {
case ROUTE_FEED_PENDING: {
- // TODO: fix this type error
return t(views[view].name)
}
case FEED_COLLECTION_LIST: {
@@ -59,3 +61,54 @@ export const useFeedHeaderTitle = () => {
}
}
}
+
+export const useAddFeedToFeedList = (options?: { onSuccess: () => void; onError: () => void }) => {
+ const { t } = useTranslation("settings")
+ return useMutation({
+ mutationFn: async (payload: { feedId: string; listId: string }) => {
+ const feed = await apiClient.lists.feeds.$post({
+ json: {
+ listId: payload.listId,
+ feedId: payload.feedId,
+ },
+ })
+
+ feedActions.addFeedToFeedList(payload.listId, feed.data)
+ },
+ onSuccess: () => {
+ toast.success(t("lists.feeds.add.success"))
+
+ options?.onSuccess?.()
+ },
+ async onError() {
+ toast.error(t("lists.feeds.add.error"))
+ options?.onError?.()
+ },
+ })
+}
+
+export const useRemoveFeedFromFeedList = (options?: {
+ onSuccess: () => void
+ onError: () => void
+}) => {
+ const { t } = useTranslation("settings")
+ return useMutation({
+ mutationFn: async (payload: { feedId: string; listId: string }) => {
+ feedActions.removeFeedFromFeedList(payload.listId, payload.feedId)
+ await apiClient.lists.feeds.$delete({
+ json: {
+ listId: payload.listId,
+ feedId: payload.feedId,
+ },
+ })
+ },
+ onSuccess: () => {
+ toast.success(t("lists.feeds.delete.success"))
+ options?.onSuccess?.()
+ },
+ async onError() {
+ toast.error(t("lists.feeds.delete.error"))
+ options?.onError?.()
+ },
+ })
+}
diff --git a/apps/renderer/src/store/feed/store.ts b/apps/renderer/src/store/feed/store.ts
index 37c8257b5..5208f18af 100644
--- a/apps/renderer/src/store/feed/store.ts
+++ b/apps/renderer/src/store/feed/store.ts
@@ -1,10 +1,17 @@
import { produce } from "immer"
+import { omit } from "lodash-es"
import { nanoid } from "nanoid"
import { whoami } from "~/atoms/user"
import { runTransactionInScope } from "~/database"
import { apiClient } from "~/lib/api-fetch"
-import type { TargetModel, UserModel } from "~/models"
+import type {
+ FeedModel,
+ FeedOrListModel,
+ FeedOrListRespModel,
+ ListModel,
+ UserModel,
+} from "~/models"
import { FeedService } from "~/services"
import { getSubscriptionByFeedId } from "../subscription"
@@ -23,9 +30,16 @@ class FeedActions {
set({ feeds: {} })
}
- upsertMany(feeds: TargetModel[]) {
+ upsertMany(feeds: FeedOrListRespModel[]) {
runTransactionInScope(() => {
- FeedService.upsertMany(feeds)
+ FeedService.upsertMany(
+ feeds.map((feed) => {
+ if (feed.type === "list") {
+ return omit(feed, ["feeds"])
+ }
+ return feed
+ }),
+ )
})
set((state) =>
produce(state, (state) => {
@@ -53,7 +67,7 @@ class FeedActions {
}
})
- state.feeds[feed.id] = feed
+ state.feeds[feed.id] = omit(feed, "feeds") as FeedOrListModel
} else {
// Store temp feed in memory
const nonce = feed["nonce"] || nanoid(8)
@@ -67,7 +81,7 @@ class FeedActions {
)
}
- private patch(feedId: string, patch: Partial) {
+ private patch(feedId: string, patch: Partial) {
set((state) =>
produce(state, (state) => {
const feed = state.feeds[feedId]
@@ -100,6 +114,27 @@ class FeedActions {
})
}
+ async addFeedToFeedList(listId: string, feed: FeedModel) {
+ const list = get().feeds[listId] as ListModel
+ if (!list) return
+ feedActions.upsertMany([feed])
+
+ this.patch(listId, {
+ feedIds: [feed.id, ...list.feedIds],
+ })
+ feedActions.upsertMany([get().feeds[listId]])
+ }
+
+ async removeFeedFromFeedList(listId: string, feedId: string) {
+ const list = get().feeds[listId] as ListModel
+ if (!list) return
+
+ this.patch(listId, {
+ feedIds: list.feedIds.filter((id) => id !== feedId),
+ })
+ feedActions.upsertMany([get().feeds[listId]])
+ }
+
// API Fetcher
//
@@ -136,17 +171,17 @@ class FeedActions {
async fetchOwnedLists() {
const res = await apiClient.lists.list.$get()
- this.upsertMany(res.data)
+ this.upsertMany(res.data.concat())
return res.data
}
}
export const feedActions = new FeedActions()
-export const getFeedById = (feedId: string): Nullable =>
+export const getFeedById = (feedId: string): Nullable =>
useFeedStore.getState().feeds[feedId]
-export const getPreferredTitle = (feed?: TargetModel | null) => {
+export const getPreferredTitle = (feed?: FeedOrListRespModel | null) => {
if (!feed?.id) {
return feed?.title
}
diff --git a/apps/renderer/src/store/feed/types.ts b/apps/renderer/src/store/feed/types.ts
index d5bde4f2f..6d5d1cf69 100644
--- a/apps/renderer/src/store/feed/types.ts
+++ b/apps/renderer/src/store/feed/types.ts
@@ -1,15 +1,15 @@
-import type { TargetModel } from "~/models"
+import type { FeedOrListModel, FeedOrListRespModel } from "~/models"
type FeedId = string
export interface FeedState {
- feeds: Record
+ feeds: Record
}
export interface FeedActions {
- upsertMany: (feeds: TargetModel[]) => void
+ upsertMany: (feeds: FeedOrListRespModel[]) => void
clear: () => void
- patch: (feedId: FeedId, patch: Partial) => void
+ patch: (feedId: FeedId, patch: Partial) => void
}
export type FeedQueryParams = { id?: string; url?: string; isList?: boolean }
diff --git a/apps/renderer/src/store/search/types.ts b/apps/renderer/src/store/search/types.ts
index c53a583a6..f5491f726 100644
--- a/apps/renderer/src/store/search/types.ts
+++ b/apps/renderer/src/store/search/types.ts
@@ -1,4 +1,4 @@
-import type { EntryModel, TargetModel } from "~/models"
+import type { EntryModel, FeedOrListRespModel } from "~/models"
import type { SubscriptionFlatModel } from "../subscription"
import type { SearchType } from "./constants"
@@ -9,7 +9,7 @@ export interface SearchResult exten
}
export interface SearchState {
- feeds: SearchResult[]
+ feeds: SearchResult[]
entries: SearchResult[]
subscriptions: SearchResult[]
diff --git a/locales/common/ar-DZ.json b/locales/common/ar-DZ.json
index 78c34d91c..33d135b7b 100644
--- a/locales/common/ar-DZ.json
+++ b/locales/common/ar-DZ.json
@@ -5,6 +5,7 @@
"confirm": "تأكيد",
"ok": "موافق",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة التي قبل الماضية",
"time.today": "اليوم",
@@ -24,6 +25,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ar-IQ.json b/locales/common/ar-IQ.json
index 09de59417..ce5e0581b 100644
--- a/locales/common/ar-IQ.json
+++ b/locales/common/ar-IQ.json
@@ -4,6 +4,7 @@
"confirm": "تأكيد",
"ok": "موافق",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة قبل الماضية",
"time.today": "اليوم",
@@ -23,6 +24,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ar-KW.json b/locales/common/ar-KW.json
index 09de59417..ce5e0581b 100644
--- a/locales/common/ar-KW.json
+++ b/locales/common/ar-KW.json
@@ -4,6 +4,7 @@
"confirm": "تأكيد",
"ok": "موافق",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة قبل الماضية",
"time.today": "اليوم",
@@ -23,6 +24,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ar-MA.json b/locales/common/ar-MA.json
index 66293ad4c..491b4cb56 100644
--- a/locales/common/ar-MA.json
+++ b/locales/common/ar-MA.json
@@ -5,6 +5,7 @@
"confirm": "تأكيد",
"ok": "موافق",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة التي سبقتها",
"time.today": "اليوم",
@@ -24,6 +25,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ar-SA.json b/locales/common/ar-SA.json
index 0ff14b728..b8d91359a 100644
--- a/locales/common/ar-SA.json
+++ b/locales/common/ar-SA.json
@@ -5,6 +5,7 @@
"confirm": "تأكيد",
"ok": "حسنًا",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة قبل الماضية",
"time.today": "اليوم",
@@ -24,6 +25,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ar-TN.json b/locales/common/ar-TN.json
index 013ae1159..b06999667 100644
--- a/locales/common/ar-TN.json
+++ b/locales/common/ar-TN.json
@@ -4,6 +4,7 @@
"confirm": "تأكيد",
"ok": "موافق",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "الليلة الماضية",
"time.the_night_before_last": "الليلة التي قبلها",
"time.today": "اليوم",
@@ -23,6 +24,5 @@
"words.result": "نتيجة",
"words.result_one": "نتيجة",
"words.result_other": "نتائج",
- "words.space": " ",
"words.which.all": "الكل"
-}
+}
\ No newline at end of file
diff --git a/locales/common/en.json b/locales/common/en.json
index d75357d1b..2be3edc21 100644
--- a/locales/common/en.json
+++ b/locales/common/en.json
@@ -5,6 +5,7 @@
"confirm": "Confirm",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Last Night",
"time.the_night_before_last": "The Night Before Last",
"time.today": "Today",
@@ -28,8 +29,7 @@
"words.result": "result",
"words.result_one": "result",
"words.result_other": "results",
- "words.space": " ",
"words.submit": "Submit",
"words.update": "Update",
"words.which.all": "All"
-}
+}
\ No newline at end of file
diff --git a/locales/common/es.json b/locales/common/es.json
index 42115fdee..a607af45c 100644
--- a/locales/common/es.json
+++ b/locales/common/es.json
@@ -5,6 +5,7 @@
"confirm": "Confirmar",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Anoche",
"time.the_night_before_last": "Anteanoche",
"time.today": "Hoy",
@@ -24,6 +25,5 @@
"words.result": "resultado",
"words.result_one": "resultado",
"words.result_other": "resultados",
- "words.space": " ",
"words.which.all": "Todo"
-}
+}
\ No newline at end of file
diff --git a/locales/common/fi.json b/locales/common/fi.json
index 06c594ab2..1ba136037 100644
--- a/locales/common/fi.json
+++ b/locales/common/fi.json
@@ -4,6 +4,7 @@
"confirm": "Vahvista",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Viime yö",
"time.the_night_before_last": "Toissa yö",
"time.today": "Tänään",
@@ -23,6 +24,5 @@
"words.result": "tulos",
"words.result_one": "tulos",
"words.result_other": "tulokset",
- "words.space": " ",
"words.which.all": "Kaikki"
-}
+}
\ No newline at end of file
diff --git a/locales/common/fr.json b/locales/common/fr.json
index 75ecb72e6..e251a333c 100644
--- a/locales/common/fr.json
+++ b/locales/common/fr.json
@@ -5,6 +5,7 @@
"confirm": "Confirmer",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "La nuit dernière",
"time.the_night_before_last": "L'avant-dernière nuit",
"time.today": "Aujourd'hui",
@@ -24,6 +25,5 @@
"words.result": "résultat",
"words.result_one": "résultat",
"words.result_other": "résultats",
- "words.space": " ",
"words.which.all": "Tous"
-}
+}
\ No newline at end of file
diff --git a/locales/common/it.json b/locales/common/it.json
index 3e46cb39d..a32756e93 100644
--- a/locales/common/it.json
+++ b/locales/common/it.json
@@ -4,6 +4,7 @@
"confirm": "Conferma",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Ieri sera",
"time.the_night_before_last": "L'altra sera",
"time.today": "Oggi",
@@ -23,6 +24,5 @@
"words.result": "risultato",
"words.result_one": "risultato",
"words.result_other": "risultati",
- "words.space": " ",
"words.which.all": "Tutti"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ja.json b/locales/common/ja.json
index d7c8fd3d4..220c111e9 100644
--- a/locales/common/ja.json
+++ b/locales/common/ja.json
@@ -5,6 +5,7 @@
"confirm": "確認",
"ok": "OK",
"quantifier.piece": "個",
+ "space": "",
"time.last_night": "昨夜",
"time.the_night_before_last": "一昨夜",
"time.today": "今日",
@@ -24,6 +25,5 @@
"words.result": "結果",
"words.result_one": "結果",
"words.result_other": "結果",
- "words.space": "",
"words.which.all": "すべて"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ko.json b/locales/common/ko.json
index 03d0a5255..f229ad428 100644
--- a/locales/common/ko.json
+++ b/locales/common/ko.json
@@ -5,6 +5,7 @@
"confirm": "확인",
"ok": "확인",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "어젯밤",
"time.the_night_before_last": "그저께 밤",
"time.today": "오늘",
@@ -27,8 +28,7 @@
"words.result": "결과",
"words.result_one": "결과",
"words.result_other": "결과들",
- "words.space": " ",
"words.submit": "제출",
"words.update": "업데이트",
"words.which.all": "모두"
-}
+}
\ No newline at end of file
diff --git a/locales/common/pt.json b/locales/common/pt.json
index 4ed5c6847..1588b31b7 100644
--- a/locales/common/pt.json
+++ b/locales/common/pt.json
@@ -5,6 +5,7 @@
"confirm": "Confirmar",
"ok": "OK",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Ontem à noite",
"time.the_night_before_last": "Anteontem à noite",
"time.today": "Hoje",
@@ -24,6 +25,5 @@
"words.result": "resultado",
"words.result_one": "resultado",
"words.result_other": "resultados",
- "words.space": " ",
"words.which.all": "Todos"
-}
+}
\ No newline at end of file
diff --git a/locales/common/ru.json b/locales/common/ru.json
index 81f46ea1d..5759f5d5f 100644
--- a/locales/common/ru.json
+++ b/locales/common/ru.json
@@ -4,6 +4,7 @@
"confirm": "Подтвердить",
"ok": "ОК",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Прошлой ночью",
"time.the_night_before_last": "Позапрошлой ночью",
"time.today": "Сегодня",
@@ -23,6 +24,5 @@
"words.result": "результат",
"words.result_one": "результат",
"words.result_other": "результаты",
- "words.space": " ",
"words.which.all": "Все"
-}
+}
\ No newline at end of file
diff --git a/locales/common/tr.json b/locales/common/tr.json
index 8dbd4ffbb..90deaa3f9 100644
--- a/locales/common/tr.json
+++ b/locales/common/tr.json
@@ -5,6 +5,7 @@
"confirm": "Onayla",
"ok": "Tamam",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "Dün Gece",
"time.the_night_before_last": "Evvelsi Gece",
"time.today": "Bugün",
@@ -27,8 +28,7 @@
"words.result": "sonuç",
"words.result_one": "sonuç",
"words.result_other": "sonuçlar",
- "words.space": " ",
"words.submit": "Gönder",
"words.update": "Güncelle",
"words.which.all": "Tümü"
-}
+}
\ No newline at end of file
diff --git a/locales/common/zh-CN.json b/locales/common/zh-CN.json
index 8c3b8ad93..701674588 100644
--- a/locales/common/zh-CN.json
+++ b/locales/common/zh-CN.json
@@ -5,6 +5,7 @@
"confirm": "确认",
"ok": "好",
"quantifier.piece": "条",
+ "space": "",
"time.last_night": "昨晚",
"time.the_night_before_last": "前天晚上",
"time.today": "今天",
@@ -28,8 +29,7 @@
"words.result": "结果",
"words.result_one": "结果",
"words.result_other": "结果",
- "words.space": "",
"words.submit": "提交",
"words.update": "更新",
"words.which.all": "全部"
-}
+}
\ No newline at end of file
diff --git a/locales/common/zh-HK.json b/locales/common/zh-HK.json
index 5fc00bd52..f17ed39ae 100644
--- a/locales/common/zh-HK.json
+++ b/locales/common/zh-HK.json
@@ -5,6 +5,7 @@
"confirm": "確認",
"ok": "確定",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "昨晚",
"time.the_night_before_last": "前晚",
"time.today": "今天",
@@ -27,8 +28,7 @@
"words.result": "結果",
"words.result_one": "結果",
"words.result_other": "結果",
- "words.space": " ",
"words.submit": "提交",
"words.update": "更新",
"words.which.all": "全部"
-}
+}
\ No newline at end of file
diff --git a/locales/common/zh-TW.json b/locales/common/zh-TW.json
index 490ba0f24..80d27909c 100644
--- a/locales/common/zh-TW.json
+++ b/locales/common/zh-TW.json
@@ -5,6 +5,7 @@
"confirm": "確認",
"ok": "確定",
"quantifier.piece": "",
+ "space": " ",
"time.last_night": "昨晚",
"time.the_night_before_last": "前晚",
"time.today": "今天",
@@ -24,6 +25,5 @@
"words.result": "結果",
"words.result_one": "結果",
"words.result_other": "多筆結果",
- "words.space": " ",
"words.which.all": "全部"
-}
+}
\ No newline at end of file