feeds-fun 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/components/DiscoveryForm.vue +5 -2
  3. package/src/components/FeedForList.vue +19 -7
  4. package/src/components/{OpenaiTokensUsage.vue → TokensCost.vue} +19 -9
  5. package/src/components/UserSettingForNotification.vue +1 -1
  6. package/src/components/collections/Block.vue +97 -0
  7. package/src/components/collections/BlockItem.vue +45 -0
  8. package/src/components/collections/DetailedItem.vue +115 -0
  9. package/src/components/collections/FeedItem.vue +62 -0
  10. package/src/components/collections/Notification.vue +11 -0
  11. package/src/components/collections/SubscribingProgress.vue +41 -0
  12. package/src/components/collections/Warning.vue +70 -0
  13. package/src/components/notifications/ApiKey.vue +42 -0
  14. package/src/components/notifications/Block.vue +61 -0
  15. package/src/components/{NotificationCreateRuleHelp.vue → notifications/CreateRuleHelp.vue} +2 -2
  16. package/src/layouts/SidePanelLayout.vue +38 -7
  17. package/src/logic/api.ts +31 -7
  18. package/src/logic/settings.ts +6 -0
  19. package/src/logic/types.ts +108 -11
  20. package/src/main.ts +26 -12
  21. package/src/stores/collections.ts +53 -0
  22. package/src/stores/feeds.ts +48 -0
  23. package/src/style.css +20 -10
  24. package/src/views/CollectionsView.vue +21 -4
  25. package/src/views/DiscoveryView.vue +6 -0
  26. package/src/views/FeedsView.vue +11 -12
  27. package/src/views/MainView.vue +7 -0
  28. package/src/views/NewsView.vue +3 -2
  29. package/src/views/SettingsView.vue +29 -21
  30. package/src/components/FeedsCollections.vue +0 -102
  31. package/src/components/NotificationCollections.vue +0 -7
  32. package/src/components/NotificationOpenaiApiKey.vue +0 -28
  33. package/src/components/Notifications.vue +0 -43
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <notifications-api-key v-if="showAPIKeyNotification" />
3
+ <collections-notification v-if="showCollectionsNotification" />
4
+ <notifications-create-rule-help v-if="showCreateRuleHelpNotification" />
5
+ <collections-warning v-if="showCollectionsWarning" />
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import {computed, ref, onUnmounted, watch} from "vue";
10
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
11
+ import {useCollectionsStore} from "@/stores/collections";
12
+
13
+ const properties = defineProps<{
14
+ apiKey: boolean;
15
+ createRuleHelp: boolean;
16
+ collectionsNotification_: boolean;
17
+ collectionsWarning_: boolean;
18
+ }>();
19
+
20
+ const collections = useCollectionsStore();
21
+ const globalSettings = useGlobalSettingsStore();
22
+
23
+ const showApiKeyMessage = computed(() => {
24
+ return (
25
+ globalSettings.userSettings &&
26
+ !globalSettings.userSettings.openai_api_key.value &&
27
+ !globalSettings.userSettings.gemini_api_key.value &&
28
+ !globalSettings.userSettings.hide_message_about_setting_up_key.value
29
+ );
30
+ });
31
+
32
+ const showCollectionsNotification = computed(() => {
33
+ return (
34
+ properties.collectionsNotification_ &&
35
+ globalSettings.userSettings &&
36
+ !globalSettings.userSettings.hide_message_about_adding_collections.value
37
+ );
38
+ });
39
+
40
+ const showCreateRuleHelpNotification = computed(() => {
41
+ return !showCollectionsNotification.value && properties.createRuleHelp;
42
+ });
43
+
44
+ const showAPIKeyNotification = computed(() => {
45
+ return (
46
+ !showCollectionsNotification.value &&
47
+ !showCreateRuleHelpNotification.value &&
48
+ properties.apiKey &&
49
+ showApiKeyMessage.value
50
+ );
51
+ });
52
+
53
+ const showCollectionsWarning = computed(() => {
54
+ return (
55
+ properties.collectionsWarning_ &&
56
+ !showCollectionsNotification.value &&
57
+ globalSettings.userSettings &&
58
+ !globalSettings.userSettings.hide_message_check_your_feed_urls.value
59
+ );
60
+ });
61
+ </script>
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <div class="ffun-info-attention">
3
- <p> Make your first rule to experience the reader's full capabilities! </p>
3
+ <p> Make your first rule to experience the full power of the Feeds Fun! </p>
4
4
 
5
5
  <ul class="list-decimal list-inside">
6
- <li>Click a tag under a news.</li>
6
+ <li>Click any tag under a news item.</li>
7
7
  <li>Select more tags if needed.</li>
8
8
  <li>Set a score for the rule.</li>
9
9
  <li>Click "Create Rule".</li>
@@ -66,25 +66,58 @@
66
66
  >
67
67
  </li>
68
68
 
69
- <li>
69
+ <li class="">
70
70
  <a
71
71
  href="/api/docs"
72
72
  target="_blank"
73
73
  class="ffun-header-link"
74
74
  style="text-decoration: none"
75
- >API&#8599;</a
75
+ >API</a
76
76
  >
77
77
  </li>
78
78
 
79
- <li>
79
+ <li v-if="settings.blog">
80
80
  <a
81
- :href="settings.githubRepo"
81
+ :href="settings.blog"
82
82
  target="_blank"
83
83
  class="ffun-header-link"
84
84
  style="text-decoration: none"
85
- >GitHub&#8599;</a
85
+ >Blog</a
86
86
  >
87
87
  </li>
88
+
89
+ <li v-if="settings.redditSubreddit">
90
+ <a
91
+ :href="settings.redditSubreddit"
92
+ target="_blank"
93
+ class="ffun-header-link text-xl align-middle"
94
+ title="Reddit"
95
+ style="text-decoration: none"
96
+ ><i class="ti ti-brand-reddit"></i
97
+ ></a>
98
+ </li>
99
+
100
+ <li v-if="settings.discordInvite">
101
+ <a
102
+ :href="settings.discordInvite"
103
+ target="_blank"
104
+ class="ffun-header-link text-xl align-middle"
105
+ title="Discord"
106
+ style="text-decoration: none"
107
+ ><i class="ti ti-brand-discord"></i
108
+ ></a>
109
+ </li>
110
+
111
+ <li v-if="settings.githubRepo">
112
+ <a
113
+ :href="settings.githubRepo"
114
+ target="_blank"
115
+ class="ffun-header-link text-xl align-middle"
116
+ title="GitHub"
117
+ style="text-decoration: none">
118
+ <i class="ti ti-brand-github"></i
119
+ ></a>
120
+ </li>
88
121
  </ul>
89
122
  </div>
90
123
 
@@ -116,13 +149,11 @@
116
149
  import {useRouter, RouterLink, RouterView} from "vue-router";
117
150
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
118
151
  import {useGlobalState} from "@/stores/globalState";
119
- import {useEntriesStore} from "@/stores/entries";
120
152
  import {useSupertokens} from "@/stores/supertokens";
121
153
  import * as e from "@/logic/enums";
122
154
  import * as settings from "@/logic/settings";
123
155
 
124
156
  const globalSettings = useGlobalSettingsStore();
125
- const entriesStore = useEntriesStore();
126
157
  const supertokens = useSupertokens();
127
158
  const globalState = useGlobalState();
128
159
 
package/src/logic/api.ts CHANGED
@@ -20,8 +20,9 @@ const API_DISCOVER_FEEDS = `${ENTRY_POINT}/discover-feeds`;
20
20
  const API_ADD_FEED = `${ENTRY_POINT}/add-feed`;
21
21
  const API_ADD_OPML = `${ENTRY_POINT}/add-opml`;
22
22
  const API_UNSUBSCRIBE = `${ENTRY_POINT}/unsubscribe`;
23
- const API_GET_FEEDS_COLLECTIONS = `${ENTRY_POINT}/get-feeds-collections`;
24
- const API_SUBSCRIBE_TO_FEEDS_COLLECTIONS = `${ENTRY_POINT}/subscribe-to-feeds-collections`;
23
+ const API_GET_COLLECTIONS = `${ENTRY_POINT}/get-collections`;
24
+ const API_GET_COLLECTION_FEEDS = `${ENTRY_POINT}/get-collection-feeds`;
25
+ const API_SUBSCRIBE_TO_COLLECTIONS = `${ENTRY_POINT}/subscribe-to-collections`;
25
26
  const API_GET_TAGS_INFO = `${ENTRY_POINT}/get-tags-info`;
26
27
  const API_GET_USER_SETTINGS = `${ENTRY_POINT}/get-user-settings`;
27
28
  const API_SET_USER_SETTING = `${ENTRY_POINT}/set-user-setting`;
@@ -186,15 +187,38 @@ export async function unsubscribe({feedId}: {feedId: t.FeedId}) {
186
187
  await post({url: API_UNSUBSCRIBE, data: {feedId: feedId}});
187
188
  }
188
189
 
189
- export async function getFeedsCollections() {
190
- const response = await post({url: API_GET_FEEDS_COLLECTIONS, data: {}});
190
+ export async function getCollections() {
191
+ const response = await post({url: API_GET_COLLECTIONS, data: {}});
191
192
 
192
- return response.collections;
193
+ const collections = [];
194
+
195
+ for (let rawCollection of response.collections) {
196
+ const collection = t.collectionFromJSON(rawCollection);
197
+ collections.push(collection);
198
+ }
199
+
200
+ return collections;
201
+ }
202
+
203
+ export async function getCollectionFeeds({collectionId}: {collectionId: t.CollectionId}) {
204
+ const response = await post({
205
+ url: API_GET_COLLECTION_FEEDS,
206
+ data: {collectionId: collectionId}
207
+ });
208
+
209
+ const feeds = [];
210
+
211
+ for (let rawFeed of response.feeds) {
212
+ const feed = t.collectionFeedInfoFromJSON(rawFeed);
213
+ feeds.push(feed);
214
+ }
215
+
216
+ return feeds;
193
217
  }
194
218
 
195
- export async function subscribeToFeedsCollections({collectionsIds}: {collectionsIds: t.FeedsCollectionId[]}) {
219
+ export async function subscribeToCollections({collectionsIds}: {collectionsIds: t.CollectionId[]}) {
196
220
  await post({
197
- url: API_SUBSCRIBE_TO_FEEDS_COLLECTIONS,
221
+ url: API_SUBSCRIBE_TO_COLLECTIONS,
198
222
  data: {collections: collectionsIds}
199
223
  });
200
224
  }
@@ -13,7 +13,10 @@ export const authMode = import.meta.env.VITE_FFUN_AUTH_MODE || AuthMode.SingleUs
13
13
  export const authSupertokensApiBasePath = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_API_BASE_PATH || "/supertokens";
14
14
  export const authSupertokensResendAfter = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_RESEND_AFTER || 60 * 1000;
15
15
 
16
+ export const blog = import.meta.env.VITE_FFUN_BLOG || "https://blog.feeds.fun";
16
17
  export const githubRepo = import.meta.env.VITE_FFUN_GITHUB_REPO || "https://github.com/Tiendil/feeds.fun";
18
+ export const discordInvite = import.meta.env.VITE_FFUN_DISCORD_INVITE || "https://discord.gg/C5RVusHQXy";
19
+ export const redditSubreddit = import.meta.env.VITE_FFUN_REDDIT_SUBREDDIT || "https://www.reddit.com/r/feedsfun/";
17
20
 
18
21
  export const plausibleEnabled = import.meta.env.VITE_FFUN_PLAUSIBLE_ENABLED == "true" || false;
19
22
  export const plausibleDomain = import.meta.env.VITE_FFUN_PLAUSIBLE_DOMAIN || "localhost";
@@ -29,7 +32,10 @@ console.log("settings.authMode", authMode);
29
32
  console.log("settings.authSupertokensApiBasePath", authSupertokensApiBasePath);
30
33
  console.log("settings.authSupertokensResendAfter", authSupertokensResendAfter);
31
34
 
35
+ console.log("settings.blog", blog);
32
36
  console.log("settings.githubRepo", githubRepo);
37
+ console.log("settings.discordInvite", discordInvite);
38
+ console.log("settings.redditSubreddit", redditSubreddit);
33
39
 
34
40
  console.log("settings.plausibleEnabled", plausibleEnabled);
35
41
  console.log("settings.plausibleDomain", plausibleDomain);
@@ -18,10 +18,10 @@ export function toRuleId(id: string): RuleId {
18
18
  return id as RuleId;
19
19
  }
20
20
 
21
- export type FeedsCollectionId = string & {readonly __brand: unique symbol};
21
+ export type CollectionId = string & {readonly __brand: unique symbol};
22
22
 
23
- export function toFeedsCollectionId(id: string): FeedsCollectionId {
24
- return id as FeedsCollectionId;
23
+ export function toCollectionId(id: string): CollectionId {
24
+ return id as CollectionId;
25
25
  }
26
26
 
27
27
  export type URL = string & {readonly __brand: unique symbol};
@@ -40,6 +40,7 @@ export class Feed {
40
40
  readonly loadedAt: Date | null;
41
41
  readonly linkedAt: Date;
42
42
  readonly isOk: boolean;
43
+ readonly collectionIds: CollectionId[];
43
44
 
44
45
  constructor({
45
46
  id,
@@ -50,7 +51,8 @@ export class Feed {
50
51
  lastError,
51
52
  loadedAt,
52
53
  linkedAt,
53
- isOk
54
+ isOk,
55
+ collectionIds
54
56
  }: {
55
57
  id: FeedId;
56
58
  title: string | null;
@@ -61,6 +63,7 @@ export class Feed {
61
63
  loadedAt: Date | null;
62
64
  linkedAt: Date;
63
65
  isOk: boolean;
66
+ collectionIds: CollectionId[];
64
67
  }) {
65
68
  this.id = id;
66
69
  this.title = title;
@@ -71,6 +74,7 @@ export class Feed {
71
74
  this.loadedAt = loadedAt;
72
75
  this.linkedAt = linkedAt;
73
76
  this.isOk = isOk;
77
+ this.collectionIds = collectionIds;
74
78
  }
75
79
  }
76
80
 
@@ -82,7 +86,8 @@ export function feedFromJSON({
82
86
  state,
83
87
  lastError,
84
88
  loadedAt,
85
- linkedAt
89
+ linkedAt,
90
+ collectionIds
86
91
  }: {
87
92
  id: string;
88
93
  title: string;
@@ -92,6 +97,7 @@ export function feedFromJSON({
92
97
  lastError: string | null;
93
98
  loadedAt: string;
94
99
  linkedAt: string;
100
+ collectionIds: string[];
95
101
  }): Feed {
96
102
  return {
97
103
  id: toFeedId(id),
@@ -102,7 +108,8 @@ export function feedFromJSON({
102
108
  lastError: lastError,
103
109
  loadedAt: loadedAt !== null ? new Date(loadedAt) : null,
104
110
  linkedAt: new Date(linkedAt),
105
- isOk: state === "loaded"
111
+ isOk: state === "loaded",
112
+ collectionIds: collectionIds.map(toCollectionId)
106
113
  };
107
114
  }
108
115
 
@@ -348,7 +355,13 @@ export function userSettingFromJSON({
348
355
  name: string;
349
356
  description: string;
350
357
  }): UserSetting {
351
- return {kind, type, value, name, description};
358
+ return {
359
+ kind,
360
+ type,
361
+ value: type === "decimal" ? parseFloat(value as string) : value,
362
+ name,
363
+ description
364
+ };
352
365
  }
353
366
 
354
367
  export class ResourceHistoryRecord {
@@ -373,12 +386,96 @@ export function resourceHistoryRecordFromJSON({
373
386
  reserved
374
387
  }: {
375
388
  intervalStartedAt: string;
376
- used: number;
377
- reserved: number;
389
+ used: number | string;
390
+ reserved: number | string;
378
391
  }): ResourceHistoryRecord {
379
392
  return new ResourceHistoryRecord({
380
393
  intervalStartedAt: new Date(intervalStartedAt),
381
- used,
382
- reserved
394
+ // TODO: refactor to use kind of Decimals and to respect input types
395
+ used: parseFloat(used as string),
396
+ reserved: parseFloat(reserved as string)
397
+ });
398
+ }
399
+
400
+ export class Collection {
401
+ readonly id: CollectionId;
402
+ readonly guiOrder: number;
403
+ readonly name: string;
404
+ readonly description: string;
405
+ readonly feedsNumber: number;
406
+
407
+ constructor({
408
+ id,
409
+ guiOrder,
410
+ name,
411
+ description,
412
+ feedsNumber
413
+ }: {
414
+ id: CollectionId;
415
+ guiOrder: number;
416
+ name: string;
417
+ description: string;
418
+ feedsNumber: number;
419
+ }) {
420
+ this.id = id;
421
+ this.guiOrder = guiOrder;
422
+ this.name = name;
423
+ this.description = description;
424
+ this.feedsNumber = feedsNumber;
425
+ }
426
+ }
427
+
428
+ export function collectionFromJSON({
429
+ id,
430
+ guiOrder,
431
+ name,
432
+ description,
433
+ feedsNumber
434
+ }: {
435
+ id: string;
436
+ guiOrder: number;
437
+ name: string;
438
+ description: string;
439
+ feedsNumber: number;
440
+ }): Collection {
441
+ return {
442
+ id: toCollectionId(id),
443
+ guiOrder: guiOrder,
444
+ name: name,
445
+ description: description,
446
+ feedsNumber: feedsNumber
447
+ };
448
+ }
449
+
450
+ export class CollectionFeedInfo {
451
+ readonly url: URL;
452
+ readonly title: string;
453
+ readonly description: string;
454
+ readonly id: FeedId;
455
+
456
+ constructor({url, title, description, id}: {url: URL; title: string; description: string; id: FeedId}) {
457
+ this.url = url;
458
+ this.title = title;
459
+ this.description = description;
460
+ this.id = id;
461
+ }
462
+ }
463
+
464
+ export function collectionFeedInfoFromJSON({
465
+ url,
466
+ title,
467
+ description,
468
+ id
469
+ }: {
470
+ url: string;
471
+ title: string;
472
+ description: string;
473
+ id: string;
474
+ }): CollectionFeedInfo {
475
+ return new CollectionFeedInfo({
476
+ url: toURL(url),
477
+ title: title,
478
+ description: description,
479
+ id: toFeedId(id)
383
480
  });
384
481
  }
package/src/main.ts CHANGED
@@ -20,19 +20,26 @@ import FeedInfo from "./components/FeedInfo.vue";
20
20
  import OpmlUpload from "./components/OPMLUpload.vue";
21
21
  import FeedForList from "./components/FeedForList.vue";
22
22
  import SupertokensLogin from "./components/SupertokensLogin.vue";
23
- import FeedsCollections from "./components/FeedsCollections.vue";
24
23
  import FfunTag from "./components/FfunTag.vue";
25
24
  import SimplePagination from "./components/SimplePagination.vue";
26
25
  import UserSetting from "./components/UserSetting.vue";
27
- import OpenaiTokensUsage from "./components/OpenaiTokensUsage.vue";
26
+ import TokensCost from "./components/TokensCost.vue";
28
27
  import FaviconElement from "./components/FaviconElement.vue";
29
- import NotificationCollections from "./components/NotificationCollections.vue";
30
- import NotificationOpenaiApiKey from "./components/NotificationOpenaiApiKey.vue";
31
- import NotificationCreateRuleHelp from "./components/NotificationCreateRuleHelp.vue";
32
- import Notifications from "./components/Notifications.vue";
33
28
  import RuleForList from "./components/RuleForList.vue";
34
29
  import UserSettingForNotification from "./components/UserSettingForNotification.vue";
35
30
 
31
+ import NotificationsApiKey from "./components/notifications/ApiKey.vue";
32
+ import NotificationsCreateRuleHelp from "./components/notifications/CreateRuleHelp.vue";
33
+ import Notifications from "./components/notifications/Block.vue";
34
+
35
+ import CollectionsNotification from "./components/collections/Notification.vue";
36
+ import CollectionsWarning from "./components/collections/Warning.vue";
37
+ import CollectionsBlock from "./components/collections/Block.vue";
38
+ import CollectionsBlockItem from "./components/collections/BlockItem.vue";
39
+ import CollectionsDetailedItem from "./components/collections/DetailedItem.vue";
40
+ import CollectionsSubscribingProgress from "./components/collections/SubscribingProgress.vue";
41
+ import CollectionsFeedItem from "./components/collections/FeedItem.vue";
42
+
36
43
  import ScoreSelector from "./inputs/ScoreSelector.vue";
37
44
  import InputMarker from "./inputs/Marker.vue";
38
45
 
@@ -64,19 +71,26 @@ app.component("FeedInfo", FeedInfo);
64
71
  app.component("OpmlUpload", OpmlUpload);
65
72
  app.component("FeedForList", FeedForList);
66
73
  app.component("SupertokensLogin", SupertokensLogin);
67
- app.component("FeedsCollections", FeedsCollections);
68
74
  app.component("FfunTag", FfunTag);
69
75
  app.component("SimplePagination", SimplePagination);
70
76
  app.component("UserSetting", UserSetting);
71
- app.component("OpenaiTokensUsage", OpenaiTokensUsage);
77
+ app.component("TokensCost", TokensCost);
72
78
  app.component("FaviconElement", FaviconElement);
73
- app.component("NotificationCollections", NotificationCollections);
74
- app.component("NotificationOpenaiApiKey", NotificationOpenaiApiKey);
75
- app.component("NotificationCreateRuleHelp", NotificationCreateRuleHelp);
76
- app.component("Notifications", Notifications);
77
79
  app.component("RuleForList", RuleForList);
78
80
  app.component("UserSettingForNotification", UserSettingForNotification);
79
81
 
82
+ app.component("NotificationsApiKey", NotificationsApiKey);
83
+ app.component("NotificationsCreateRuleHelp", NotificationsCreateRuleHelp);
84
+ app.component("Notifications", Notifications);
85
+
86
+ app.component("CollectionsNotification", CollectionsNotification);
87
+ app.component("CollectionsWarning", CollectionsWarning);
88
+ app.component("CollectionsBlock", CollectionsBlock);
89
+ app.component("CollectionsBlockItem", CollectionsBlockItem);
90
+ app.component("CollectionsDetailedItem", CollectionsDetailedItem);
91
+ app.component("CollectionsSubscribingProgress", CollectionsSubscribingProgress);
92
+ app.component("CollectionsFeedItem", CollectionsFeedItem);
93
+
80
94
  app.component("ScoreSelector", ScoreSelector);
81
95
  app.component("InputMarker", InputMarker);
82
96
 
@@ -0,0 +1,53 @@
1
+ import {computed, ref, watch} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+
5
+ import type * as t from "@/logic/types";
6
+ import * as e from "@/logic/enums";
7
+ import * as api from "@/logic/api";
8
+ import {Timer} from "@/logic/timer";
9
+ import {computedAsync} from "@vueuse/core";
10
+
11
+ export const useCollectionsStore = defineStore("collectionsStore", () => {
12
+ const feeds = ref<{[id: t.CollectionId]: t.CollectionFeedInfo[]}>({});
13
+
14
+ const collections = computedAsync(async () => {
15
+ const collectionsList = await api.getCollections();
16
+
17
+ const collections: {[key: t.CollectionId]: t.Collection} = {};
18
+
19
+ for (const collection of collectionsList) {
20
+ collections[collection.id] = collection;
21
+ }
22
+
23
+ return collections;
24
+ }, {});
25
+
26
+ const collectionsOrder = computed(() => {
27
+ const order = Object.keys(collections.value) as t.CollectionId[];
28
+
29
+ order.sort((a, b) => {
30
+ return collections.value[a].guiOrder - collections.value[b].guiOrder;
31
+ });
32
+
33
+ return order;
34
+ });
35
+
36
+ async function getFeeds({collectionId}: {collectionId: t.CollectionId}) {
37
+ if (collectionId in feeds.value) {
38
+ return feeds.value[collectionId];
39
+ }
40
+
41
+ const feedsList = await api.getCollectionFeeds({collectionId: collectionId});
42
+
43
+ feeds.value[collectionId] = feedsList;
44
+
45
+ return feeds.value[collectionId];
46
+ }
47
+
48
+ return {
49
+ collections,
50
+ collectionsOrder,
51
+ getFeeds
52
+ };
53
+ });
@@ -0,0 +1,48 @@
1
+ import {computed, ref, watch} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+
5
+ import type * as t from "@/logic/types";
6
+ import * as e from "@/logic/enums";
7
+ import * as api from "@/logic/api";
8
+ import {Timer} from "@/logic/timer";
9
+ import {computedAsync} from "@vueuse/core";
10
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
11
+
12
+ export const useFeedsStore = defineStore("feedsStore", () => {
13
+ const globalSettings = useGlobalSettingsStore();
14
+
15
+ const feeds = computedAsync(async () => {
16
+ // force refresh
17
+ globalSettings.dataVersion;
18
+
19
+ const feedsList = await api.getFeeds();
20
+
21
+ const feedsDict: {[key: t.FeedId]: t.Feed} = {};
22
+
23
+ for (const feed of feedsList) {
24
+ feedsDict[feed.id] = feed;
25
+ }
26
+
27
+ return feedsDict;
28
+ }, {});
29
+
30
+ async function unsubscribe(feedId: t.FeedId) {
31
+ await api.unsubscribe({feedId: feedId});
32
+ globalSettings.updateDataVersion();
33
+ }
34
+
35
+ async function subscribe(url: t.URL) {
36
+ await api.addFeed({
37
+ url: url
38
+ });
39
+
40
+ globalSettings.updateDataVersion();
41
+ }
42
+
43
+ return {
44
+ feeds,
45
+ unsubscribe,
46
+ subscribe
47
+ };
48
+ });
package/src/style.css CHANGED
@@ -1,3 +1,5 @@
1
+ @import url("https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css");
2
+
1
3
  @tailwind base;
2
4
  @tailwind components;
3
5
  @tailwind utilities;
@@ -15,6 +17,10 @@
15
17
  @apply text-2xl font-semibold leading-relaxed my-3;
16
18
  }
17
19
 
20
+ h3 {
21
+ @apply text-xl font-semibold leading-relaxed my-2;
22
+ }
23
+
18
24
  hr {
19
25
  @apply border-slate-400 my-2;
20
26
  }
@@ -30,28 +36,36 @@
30
36
  @apply text-blue-600 hover:text-blue-800 cursor-pointer;
31
37
  }
32
38
 
39
+ .ffun-normalized-text ul {
40
+ @apply list-disc list-inside;
41
+ }
42
+
43
+ .ffun-normalized-text code {
44
+ @apply bg-slate-800 px-1 rounded bg-opacity-10 text-sm;
45
+ }
46
+
33
47
  .ffun-info-common {
34
- @apply border-2 p-2 my-1 rounded;
48
+ @apply ffun-normalized-text border-2 p-2 my-1 rounded border-slate-200;
35
49
  }
36
50
 
37
51
  .ffun-info-good {
38
- @apply ffun-info-common border-green-300 bg-green-50;
52
+ @apply ffun-info-common bg-green-50;
39
53
  }
40
54
 
41
55
  .ffun-info-bad {
42
- @apply ffun-info-common border-red-300 bg-red-50;
56
+ @apply ffun-info-common bg-red-50;
43
57
  }
44
58
 
45
59
  .ffun-info-warning {
46
- @apply ffun-info-common border-orange-300 bg-orange-50;
60
+ @apply ffun-info-common bg-orange-50;
47
61
  }
48
62
 
49
63
  .ffun-info-attention {
50
- @apply ffun-info-common border-purple-300 bg-purple-50;
64
+ @apply ffun-info-common bg-purple-50;
51
65
  }
52
66
 
53
67
  .ffun-header-link {
54
- @apply text-blue-600 hover:text-blue-800 cursor-pointer text-lg;
68
+ @apply text-blue-600 hover:text-blue-900 cursor-pointer text-lg;
55
69
  }
56
70
 
57
71
  .ffun-normal-link {
@@ -85,8 +99,4 @@
85
99
  .ffun-checkbox {
86
100
  @apply ffun-input-common h-4 w-4 align-middle;
87
101
  }
88
-
89
- .ffun-normalized-text ul {
90
- @apply list-disc list-inside;
91
- }
92
102
  }