diff --git a/ffun/ffun/api/entities.py b/ffun/ffun/api/entities.py index 91d7b672..6af3c421 100644 --- a/ffun/ffun/api/entities.py +++ b/ffun/ffun/api/entities.py @@ -270,10 +270,12 @@ class CollectionFeedInfo(pydantic.BaseModel): url: str title: str description: str + id: f_entities.FeedId @classmethod def from_internal(cls, record: fc_entities.FeedInfo) -> "CollectionFeedInfo": - return cls(url=record.url, title=record.title, description=record.description) + assert record.feed_id is not None + return cls(url=record.url, title=record.title, description=record.description, id=record.feed_id) ################## diff --git a/site/src/components/DiscoveryForm.vue b/site/src/components/DiscoveryForm.vue index 7f2e58f5..18c828e3 100644 --- a/site/src/components/DiscoveryForm.vue +++ b/site/src/components/DiscoveryForm.vue @@ -59,6 +59,9 @@ import * as api from "@/logic/api"; import {computedAsync} from "@vueuse/core"; import {useEntriesStore} from "@/stores/entries"; + import {useFeedsStore} from "@/stores/feeds"; + + const feedsStore = useFeedsStore(); const search = ref(""); @@ -91,10 +94,10 @@ return feeds; }, null); - async function addFeed(url: string) { + async function addFeed(url: t.URL) { adding.value = true; - await api.addFeed({url: url}); + await feedsStore.subscribe(url); addedFeeds.value[url] = true; diff --git a/site/src/components/FeedForList.vue b/site/src/components/FeedForList.vue index 4604d388..afeec708 100644 --- a/site/src/components/FeedForList.vue +++ b/site/src/components/FeedForList.vue @@ -6,7 +6,7 @@ + @click.prevent="feedsStore.unsubscribe(feed.id)"> remove @@ -82,11 +82,11 @@ import {computedAsync} from "@vueuse/core"; import DOMPurify from "dompurify"; import {useGlobalSettingsStore} from "@/stores/globalSettings"; - + import {useFeedsStore} from "@/stores/feeds"; import {useCollectionsStore} from "@/stores/collections"; const globalSettings = useGlobalSettingsStore(); - + const feedsStore = useFeedsStore(); const collections = useCollectionsStore(); const properties = defineProps<{feed: t.Feed}>(); @@ -111,9 +111,4 @@ } return DOMPurify.sanitize(properties.feed.description); }); - - async function unsubscribe() { - await api.unsubscribe({feedId: properties.feed.id}); - globalSettings.updateDataVersion(); - } diff --git a/site/src/components/collections/DetailedItem.vue b/site/src/components/collections/DetailedItem.vue index 2837ee16..89b53e01 100644 --- a/site/src/components/collections/DetailedItem.vue +++ b/site/src/components/collections/DetailedItem.vue @@ -99,9 +99,13 @@ showFeeds.value = false; } - const feeds = computedAsync(async () => { - return await collections.getFeeds({collectionId: properties.collectionId}); - }, []); + const feeds = computedAsync( + async () => { + return await collections.getFeeds({collectionId: properties.collectionId}); + }, + [], + {lazy: true} + ); diff --git a/site/src/layouts/SidePanelLayout.vue b/site/src/layouts/SidePanelLayout.vue index bc6800fc..e728da8d 100644 --- a/site/src/layouts/SidePanelLayout.vue +++ b/site/src/layouts/SidePanelLayout.vue @@ -149,13 +149,11 @@ import {useRouter, RouterLink, RouterView} from "vue-router"; import {useGlobalSettingsStore} from "@/stores/globalSettings"; import {useGlobalState} from "@/stores/globalState"; - import {useEntriesStore} from "@/stores/entries"; import {useSupertokens} from "@/stores/supertokens"; import * as e from "@/logic/enums"; import * as settings from "@/logic/settings"; const globalSettings = useGlobalSettingsStore(); - const entriesStore = useEntriesStore(); const supertokens = useSupertokens(); const globalState = useGlobalState(); diff --git a/site/src/logic/types.ts b/site/src/logic/types.ts index 30ce4af7..7c329879 100644 --- a/site/src/logic/types.ts +++ b/site/src/logic/types.ts @@ -451,26 +451,31 @@ export class CollectionFeedInfo { readonly url: URL; readonly title: string; readonly description: string; + readonly id: FeedId; - constructor({url, title, description}: {url: URL; title: string; description: string}) { + constructor({url, title, description, id}: {url: URL; title: string; description: string; id: FeedId}) { this.url = url; this.title = title; this.description = description; + this.id = id; } } export function collectionFeedInfoFromJSON({ url, title, - description + description, + id }: { url: string; title: string; description: string; + id: string; }): CollectionFeedInfo { return new CollectionFeedInfo({ url: toURL(url), title: title, - description: description + description: description, + id: toFeedId(id) }); } diff --git a/site/src/stores/feeds.ts b/site/src/stores/feeds.ts new file mode 100644 index 00000000..875afba6 --- /dev/null +++ b/site/src/stores/feeds.ts @@ -0,0 +1,48 @@ +import {computed, ref, watch} from "vue"; +import {useRouter} from "vue-router"; +import {defineStore} from "pinia"; + +import type * as t from "@/logic/types"; +import * as e from "@/logic/enums"; +import * as api from "@/logic/api"; +import {Timer} from "@/logic/timer"; +import {computedAsync} from "@vueuse/core"; +import {useGlobalSettingsStore} from "@/stores/globalSettings"; + +export const useFeedsStore = defineStore("feedsStore", () => { + const globalSettings = useGlobalSettingsStore(); + + const feeds = computedAsync(async () => { + // force refresh + globalSettings.dataVersion; + + const feedsList = await api.getFeeds(); + + const feedsDict: {[key: t.FeedId]: t.Feed} = {}; + + for (const feed of feedsList) { + feedsDict[feed.id] = feed; + } + + return feedsDict; + }, {}); + + async function unsubscribe(feedId: t.FeedId) { + await api.unsubscribe({feedId: feedId}); + globalSettings.updateDataVersion(); + } + + async function subscribe(url: t.URL) { + await api.addFeed({ + url: url + }); + + globalSettings.updateDataVersion(); + } + + return { + feeds, + unsubscribe, + subscribe + }; +}); diff --git a/site/src/views/CollectionsView.vue b/site/src/views/CollectionsView.vue index 46c3f4e6..524b6f2d 100644 --- a/site/src/views/CollectionsView.vue +++ b/site/src/views/CollectionsView.vue @@ -18,7 +18,6 @@ import * as t from "@/logic/types"; import * as e from "@/logic/enums"; import {useGlobalSettingsStore} from "@/stores/globalSettings"; - import {useEntriesStore} from "@/stores/entries"; import {useCollectionsStore} from "@/stores/collections"; const globalSettings = useGlobalSettingsStore(); diff --git a/site/src/views/FeedsView.vue b/site/src/views/FeedsView.vue index c900e48c..f6fece64 100644 --- a/site/src/views/FeedsView.vue +++ b/site/src/views/FeedsView.vue @@ -49,27 +49,24 @@ import {computed, ref, onUnmounted, watch} from "vue"; import {computedAsync} from "@vueuse/core"; import {useGlobalSettingsStore} from "@/stores/globalSettings"; + import {useFeedsStore} from "@/stores/feeds"; import * as api from "@/logic/api"; import type * as t from "@/logic/types"; import * as e from "@/logic/enums"; const globalSettings = useGlobalSettingsStore(); - globalSettings.mainPanelMode = e.MainPanelMode.Feeds; + const feedsStore = useFeedsStore(); - const feeds = computedAsync(async () => { - // force refresh - globalSettings.dataVersion; - return await api.getFeeds(); - }, null); + globalSettings.mainPanelMode = e.MainPanelMode.Feeds; const sortedFeeds = computed(() => { - if (!feeds.value) { + let sorted = Object.values(feedsStore.feeds); + + if (sorted.length === 0) { return null; } - let sorted = feeds.value.slice(); - const orderProperties = e.FeedsOrderProperties.get(globalSettings.feedsOrder); if (!orderProperties) {