feeds-fun 1.11.1 → 1.12.1

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 (32) 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 +0 -2
  17. package/src/logic/api.ts +31 -7
  18. package/src/logic/types.ts +108 -11
  19. package/src/main.ts +26 -12
  20. package/src/stores/collections.ts +53 -0
  21. package/src/stores/feeds.ts +48 -0
  22. package/src/style.css +17 -9
  23. package/src/views/CollectionsView.vue +21 -4
  24. package/src/views/DiscoveryView.vue +6 -0
  25. package/src/views/FeedsView.vue +11 -12
  26. package/src/views/MainView.vue +7 -0
  27. package/src/views/NewsView.vue +3 -2
  28. package/src/views/SettingsView.vue +29 -21
  29. package/src/components/FeedsCollections.vue +0 -102
  30. package/src/components/NotificationCollections.vue +0 -7
  31. package/src/components/NotificationOpenaiApiKey.vue +0 -28
  32. 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>
@@ -149,13 +149,11 @@
149
149
  import {useRouter, RouterLink, RouterView} from "vue-router";
150
150
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
151
151
  import {useGlobalState} from "@/stores/globalState";
152
- import {useEntriesStore} from "@/stores/entries";
153
152
  import {useSupertokens} from "@/stores/supertokens";
154
153
  import * as e from "@/logic/enums";
155
154
  import * as settings from "@/logic/settings";
156
155
 
157
156
  const globalSettings = useGlobalSettingsStore();
158
- const entriesStore = useEntriesStore();
159
157
  const supertokens = useSupertokens();
160
158
  const globalState = useGlobalState();
161
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
  }
@@ -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
@@ -17,6 +17,10 @@
17
17
  @apply text-2xl font-semibold leading-relaxed my-3;
18
18
  }
19
19
 
20
+ h3 {
21
+ @apply text-xl font-semibold leading-relaxed my-2;
22
+ }
23
+
20
24
  hr {
21
25
  @apply border-slate-400 my-2;
22
26
  }
@@ -32,24 +36,32 @@
32
36
  @apply text-blue-600 hover:text-blue-800 cursor-pointer;
33
37
  }
34
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
+
35
47
  .ffun-info-common {
36
- @apply border-2 p-2 my-1 rounded;
48
+ @apply ffun-normalized-text border-2 p-2 my-1 rounded border-slate-200;
37
49
  }
38
50
 
39
51
  .ffun-info-good {
40
- @apply ffun-info-common border-green-300 bg-green-50;
52
+ @apply ffun-info-common bg-green-50;
41
53
  }
42
54
 
43
55
  .ffun-info-bad {
44
- @apply ffun-info-common border-red-300 bg-red-50;
56
+ @apply ffun-info-common bg-red-50;
45
57
  }
46
58
 
47
59
  .ffun-info-warning {
48
- @apply ffun-info-common border-orange-300 bg-orange-50;
60
+ @apply ffun-info-common bg-orange-50;
49
61
  }
50
62
 
51
63
  .ffun-info-attention {
52
- @apply ffun-info-common border-purple-300 bg-purple-50;
64
+ @apply ffun-info-common bg-purple-50;
53
65
  }
54
66
 
55
67
  .ffun-header-link {
@@ -87,8 +99,4 @@
87
99
  .ffun-checkbox {
88
100
  @apply ffun-input-common h-4 w-4 align-middle;
89
101
  }
90
-
91
- .ffun-normalized-text ul {
92
- @apply list-disc list-inside;
93
- }
94
102
  }
@@ -2,9 +2,20 @@
2
2
  <side-panel-layout :reload-button="false">
3
3
  <template #main-header> Collections </template>
4
4
 
5
- <p> Try to subscribe for the feeds collections that we are preparing for you! </p>
5
+ <div class="ffun-info-attention">
6
+ <p
7
+ >We've prepared some ready-to-use thematic collections just for you. All the feeds in them are fully tagged
8
+ free of charge.</p
9
+ >
10
+ <p>Subscribe to some and enjoy the full power of Feeds Fun!</p>
11
+ </div>
6
12
 
7
- <feeds-collections />
13
+ <div
14
+ v-for="collectionId in collections.collectionsOrder"
15
+ :key="collectionId"
16
+ class="collection-block pb-4">
17
+ <collections-detailed-item :collectionId="collectionId" />
18
+ </div>
8
19
  </side-panel-layout>
9
20
  </template>
10
21
 
@@ -15,11 +26,17 @@
15
26
  import * as t from "@/logic/types";
16
27
  import * as e from "@/logic/enums";
17
28
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
18
- import {useEntriesStore} from "@/stores/entries";
29
+ import {useCollectionsStore} from "@/stores/collections";
19
30
 
20
31
  const globalSettings = useGlobalSettingsStore();
21
32
 
33
+ const collections = useCollectionsStore();
34
+
22
35
  globalSettings.mainPanelMode = e.MainPanelMode.Collections;
23
36
  </script>
24
37
 
25
- <style></style>
38
+ <style scoped>
39
+ .collection-block:not(:last-child) {
40
+ border-bottom-width: 1px;
41
+ }
42
+ </style>
@@ -2,6 +2,12 @@
2
2
  <side-panel-layout :reload-button="false">
3
3
  <template #main-header> Discovery </template>
4
4
 
5
+ <notifications
6
+ :create-rule-help="false"
7
+ :api-key="false"
8
+ :collections-notification_="false"
9
+ :collections-warning_="true" />
10
+
5
11
  <h2>Lood feeds from an OPML file</h2>
6
12
 
7
13
  <opml-upload />
@@ -32,8 +32,10 @@
32
32
 
33
33
  <notifications
34
34
  v-if="sortedFeeds !== null"
35
- :openai-api-key="false"
36
- :collections="sortedFeeds === null || sortedFeeds.length == 0" />
35
+ :create-rule-help="false"
36
+ :api-key="false"
37
+ :collections-notification_="sortedFeeds === null || sortedFeeds.length == 0"
38
+ :collections-warning_="true" />
37
39
 
38
40
  <feeds-list
39
41
  v-if="sortedFeeds"
@@ -48,26 +50,23 @@
48
50
  import {computed, ref, onUnmounted, watch} from "vue";
49
51
  import {computedAsync} from "@vueuse/core";
50
52
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
53
+ import {useFeedsStore} from "@/stores/feeds";
51
54
  import * as api from "@/logic/api";
52
55
  import type * as t from "@/logic/types";
53
56
  import * as e from "@/logic/enums";
54
57
 
55
58
  const globalSettings = useGlobalSettingsStore();
56
59
 
57
- globalSettings.mainPanelMode = e.MainPanelMode.Feeds;
60
+ const feedsStore = useFeedsStore();
58
61
 
59
- const feeds = computedAsync(async () => {
60
- // force refresh
61
- globalSettings.dataVersion;
62
- return await api.getFeeds();
63
- }, null);
62
+ globalSettings.mainPanelMode = e.MainPanelMode.Feeds;
64
63
 
65
64
  const sortedFeeds = computed(() => {
66
- if (!feeds.value) {
67
- return null;
68
- }
65
+ let sorted = Object.values(feedsStore.feeds);
69
66
 
70
- let sorted = feeds.value.slice();
67
+ if (sorted.length === 0) {
68
+ return [];
69
+ }
71
70
 
72
71
  const orderProperties = e.FeedsOrderProperties.get(globalSettings.feedsOrder);
73
72
 
@@ -18,6 +18,13 @@
18
18
  <li>Filter and sort news how you want ⇒ read only what you want.</li>
19
19
  </ul>
20
20
 
21
+ <h2>Curated collections of feeds</h2>
22
+
23
+ <ul class="list-disc list-inside text-left">
24
+ <li>We handpick the most interesting feeds into thematic collections.</li>
25
+ <li>Feeds in the collections are tagged for free.</li>
26
+ </ul>
27
+
21
28
  <h2>You are in control</h2>
22
29
 
23
30
  <ul class="list-disc list-inside text-left">