feeds-fun 1.11.1 → 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 (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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feeds-fun",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "author": "Aliaksei Yaletski (Tiendil) <a.eletsky@gmail.com> (https://tiendil.org/)",
5
5
  "description": "Frontend for the Feeds Fun — web-based news reader",
6
6
  "keywords": [
@@ -59,6 +59,9 @@
59
59
  import * as api from "@/logic/api";
60
60
  import {computedAsync} from "@vueuse/core";
61
61
  import {useEntriesStore} from "@/stores/entries";
62
+ import {useFeedsStore} from "@/stores/feeds";
63
+
64
+ const feedsStore = useFeedsStore();
62
65
 
63
66
  const search = ref("");
64
67
 
@@ -91,10 +94,10 @@
91
94
  return feeds;
92
95
  }, null);
93
96
 
94
- async function addFeed(url: string) {
97
+ async function addFeed(url: t.URL) {
95
98
  adding.value = true;
96
99
 
97
- await api.addFeed({url: url});
100
+ await feedsStore.subscribe(url);
98
101
 
99
102
  addedFeeds.value[url] = true;
100
103
 
@@ -6,7 +6,7 @@
6
6
  <a
7
7
  href="#"
8
8
  class="ffun-normal-link"
9
- @click.prevent="unsubscribe()">
9
+ @click.prevent="feedsStore.unsubscribe(feed.id)">
10
10
  remove
11
11
  </a>
12
12
  </div>
@@ -53,9 +53,22 @@
53
53
  class="ffun-normal-link"
54
54
  :value="feed.url"
55
55
  :text="purifiedTitle" />
56
+
57
+ <template v-if="feed.collectionIds.length > 0">
58
+ <span v-for="(collectionId, index) in feed.collectionIds">
59
+ <template v-if="collectionId in collections.collections">
60
+ <br />
61
+ Collections:
62
+ <span class="text-green-700 font-bold">{{ collections.collections[collectionId].name }}</span
63
+ ><span v-if="index < feed.collectionIds.length - 1">, </span>
64
+ </template>
65
+ </span>
66
+ </template>
56
67
  <template v-if="globalSettings.showFeedsDescriptions">
57
68
  <br />
58
- <div v-html="purifiedDescription" />
69
+ <div class="max-w-3xl flex-1 bg-slate-50 border-2 rounded p-4">
70
+ <div v-html="purifiedDescription" />
71
+ </div>
59
72
  </template>
60
73
  </div>
61
74
  </div>
@@ -69,8 +82,12 @@
69
82
  import {computedAsync} from "@vueuse/core";
70
83
  import DOMPurify from "dompurify";
71
84
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
85
+ import {useFeedsStore} from "@/stores/feeds";
86
+ import {useCollectionsStore} from "@/stores/collections";
72
87
 
73
88
  const globalSettings = useGlobalSettingsStore();
89
+ const feedsStore = useFeedsStore();
90
+ const collections = useCollectionsStore();
74
91
 
75
92
  const properties = defineProps<{feed: t.Feed}>();
76
93
 
@@ -94,9 +111,4 @@
94
111
  }
95
112
  return DOMPurify.sanitize(properties.feed.description);
96
113
  });
97
-
98
- async function unsubscribe() {
99
- await api.unsubscribe({feedId: properties.feed.id});
100
- globalSettings.updateDataVersion();
101
- }
102
114
  </script>
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <tr>
3
3
  <td>{{ period }}</td>
4
- <td style="text-align: right">{{ usage.used }}</td>
5
- <td style="text-align: right">{{ usage.reserved }}</td>
6
- <td style="text-align: right">{{ usage.total() }}</td>
4
+ <td style="text-align: right">{{ cost_used }}</td>
5
+ <td style="text-align: right">{{ cost_reserved }}</td>
6
+ <td style="text-align: right">{{ cost_total }}</td>
7
7
  <td style="text-align: right">{{ percents }}%</td>
8
8
  </tr>
9
9
  </template>
@@ -24,20 +24,18 @@
24
24
 
25
25
  globalSettings.mainPanelMode = e.MainPanelMode.Settings;
26
26
 
27
- const openAIUsage = computedAsync(async () => {
28
- return await api.getResourceHistory({kind: "openai_tokens"});
29
- }, null);
30
-
31
27
  const period = computed(() => {
32
28
  return properties.usage.intervalStartedAt.toLocaleString("default", {month: "long", year: "numeric"});
33
29
  });
34
30
 
31
+ const k = 2;
32
+
35
33
  const percents = computed(() => {
36
34
  if (globalSettings.userSettings == null) {
37
35
  return "—";
38
36
  }
39
37
 
40
- const setting = globalSettings.userSettings["openai_max_tokens_in_month"];
38
+ const setting = globalSettings.userSettings["max_tokens_cost_in_month"];
41
39
 
42
40
  if (!setting) {
43
41
  return "—";
@@ -54,7 +52,19 @@
54
52
  return "—";
55
53
  }
56
54
 
57
- return ((total / limit) * 100).toFixed(5);
55
+ return ((total / limit) * 100).toFixed(k);
56
+ });
57
+
58
+ const cost_used = computed(() => {
59
+ return properties.usage.used.toFixed(k);
60
+ });
61
+
62
+ const cost_reserved = computed(() => {
63
+ return properties.usage.reserved.toFixed(k);
64
+ });
65
+
66
+ const cost_total = computed(() => {
67
+ return properties.usage.total().toFixed(k);
58
68
  });
59
69
  </script>
60
70
 
@@ -2,7 +2,7 @@
2
2
  <button
3
3
  v-if="setting"
4
4
  @click.prevent="updateFlag(true)"
5
- class="ffun-form-button"
5
+ class="ffun-normal-link"
6
6
  >{{ text }}</button
7
7
  >
8
8
  </template>
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <div>
3
+ <form @submit.prevent="subscribe">
4
+ <collections-block-item
5
+ v-for="collectionId in collections.collectionsOrder"
6
+ v-model="selectedCollections"
7
+ :collectionId="collectionId"
8
+ :selectedCollections="selectedCollections" />
9
+
10
+ <button
11
+ type="submit"
12
+ class="ffun-form-button"
13
+ >Subscribe</button
14
+ >
15
+
16
+ <button
17
+ type="button"
18
+ class="ffun-form-button ml-2"
19
+ @click.prevent="router.push({name: e.MainPanelMode.Collections, params: {}})"
20
+ >Explore Feeds Library
21
+ </button>
22
+
23
+ <user-setting-for-notification
24
+ class="ml-2"
25
+ kind="hide_message_about_adding_collections"
26
+ button-text="Hide this message" />
27
+ </form>
28
+
29
+ <collections-subscribing-progress
30
+ :loading="loading"
31
+ :loaded="loaded"
32
+ :error="error" />
33
+ </div>
34
+ </template>
35
+
36
+ <script lang="ts" setup>
37
+ import {useRouter, RouterLink, RouterView} from "vue-router";
38
+ import {computed, ref, watch} from "vue";
39
+ import type * as t from "@/logic/types";
40
+ import * as e from "@/logic/enums";
41
+ import * as api from "@/logic/api";
42
+ import {computedAsync} from "@vueuse/core";
43
+ import DOMPurify from "dompurify";
44
+ import {useEntriesStore} from "@/stores/entries";
45
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
46
+ import {useCollectionsStore} from "@/stores/collections";
47
+
48
+ const router = useRouter();
49
+
50
+ const loading = ref(false);
51
+ const loaded = ref(false);
52
+ const error = ref(false);
53
+
54
+ const globalSettings = useGlobalSettingsStore();
55
+
56
+ const collections = useCollectionsStore();
57
+
58
+ const selectedCollections = ref<t.CollectionId[]>([]);
59
+
60
+ // fill selectedCollections in case collections are already loaded
61
+ selectedCollections.value.push(...collections.collectionsOrder);
62
+
63
+ // fill selectedCollections in case collections are not loaded yet
64
+ watch(
65
+ () => collections.collectionsOrder,
66
+ (newOrder) => {
67
+ selectedCollections.value.push(...newOrder);
68
+ },
69
+ {once: true}
70
+ );
71
+
72
+ async function subscribe() {
73
+ loading.value = true;
74
+ loaded.value = false;
75
+ error.value = false;
76
+
77
+ try {
78
+ await api.subscribeToCollections({
79
+ collectionsIds: selectedCollections.value
80
+ });
81
+
82
+ loading.value = false;
83
+ loaded.value = true;
84
+ error.value = false;
85
+ } catch (e) {
86
+ console.error(e);
87
+
88
+ loading.value = false;
89
+ loaded.value = false;
90
+ error.value = true;
91
+ }
92
+
93
+ globalSettings.updateDataVersion();
94
+ }
95
+ </script>
96
+
97
+ <style scoped></style>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <div>
3
+ <input
4
+ class="ffun-checkbox align-top m-1"
5
+ type="checkbox"
6
+ :id="collection.id"
7
+ :name="collection.name"
8
+ :value="collection.id"
9
+ v-model="model"
10
+ checked />
11
+ <label
12
+ class="ml-2"
13
+ :for="collection.id">
14
+ <div class="inline-block">
15
+ <span class="text-green-700 font-bold">{{ collection.name }}</span> [feeds: {{ collection.feedsNumber }}]
16
+ <p class="">{{ collection.description }}</p>
17
+ </div>
18
+ </label>
19
+ </div>
20
+ </template>
21
+
22
+ <script lang="ts" setup>
23
+ import {computed, ref} from "vue";
24
+ import type * as t from "@/logic/types";
25
+ import * as e from "@/logic/enums";
26
+ import * as api from "@/logic/api";
27
+ import {computedAsync} from "@vueuse/core";
28
+ import DOMPurify from "dompurify";
29
+ import {useEntriesStore} from "@/stores/entries";
30
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
31
+ import {useCollectionsStore} from "@/stores/collections";
32
+
33
+ const properties = defineProps<{
34
+ collectionId: t.CollectionId;
35
+ selectedCollections: t.CollectionId[];
36
+ }>();
37
+
38
+ const model = defineModel();
39
+
40
+ const collections = useCollectionsStore();
41
+
42
+ const collection = computed(() => collections.collections[properties.collectionId]);
43
+ </script>
44
+
45
+ <style scoped></style>
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <div>
3
+ <h3>{{ collection.name }}</h3>
4
+ <p class="">{{ collection.description }}</p>
5
+
6
+ <div v-if="showFeeds">
7
+ <div
8
+ v-for="feed in feeds"
9
+ :key="feed.url"
10
+ class="mb-2 pb-2 collection-feed-block">
11
+ <collections-feed-item :feed="feed" />
12
+ </div>
13
+ </div>
14
+
15
+ <button
16
+ @click.prevent="subscribe"
17
+ class="ffun-form-button mr-2">
18
+ <template v-if="collection.feedsNumber === 1"> Subscribe to 1 feed </template>
19
+
20
+ <template v-else> Subscribe to all {{ collection.feedsNumber }} feeds </template>
21
+ </button>
22
+
23
+ <button
24
+ v-if="!showFeeds"
25
+ @click.prevent="show"
26
+ class="ffun-form-button"
27
+ >Show feeds</button
28
+ >
29
+
30
+ <button
31
+ v-if="showFeeds"
32
+ @click.prevent="hide"
33
+ class="ffun-form-button"
34
+ >Hide feeds</button
35
+ >
36
+
37
+ <collections-subscribing-progress
38
+ :loading="loading"
39
+ :loaded="loaded"
40
+ :error="error" />
41
+ </div>
42
+ </template>
43
+
44
+ <script lang="ts" setup>
45
+ import {computed, ref} from "vue";
46
+ import type * as t from "@/logic/types";
47
+ import * as e from "@/logic/enums";
48
+ import * as api from "@/logic/api";
49
+ import {computedAsync} from "@vueuse/core";
50
+ import DOMPurify from "dompurify";
51
+ import {useEntriesStore} from "@/stores/entries";
52
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
53
+ import {useCollectionsStore} from "@/stores/collections";
54
+
55
+ const properties = defineProps<{
56
+ collectionId: t.CollectionId;
57
+ }>();
58
+
59
+ const globalSettings = useGlobalSettingsStore();
60
+
61
+ const collections = useCollectionsStore();
62
+
63
+ const collection = computed(() => collections.collections[properties.collectionId]);
64
+
65
+ const loading = ref(false);
66
+ const loaded = ref(false);
67
+ const error = ref(false);
68
+ const showFeeds = ref(false);
69
+
70
+ async function subscribe() {
71
+ loading.value = true;
72
+ loaded.value = false;
73
+ error.value = false;
74
+
75
+ try {
76
+ await api.subscribeToCollections({
77
+ collectionsIds: [properties.collectionId]
78
+ });
79
+
80
+ loading.value = false;
81
+ loaded.value = true;
82
+ error.value = false;
83
+ } catch (e) {
84
+ console.error(e);
85
+
86
+ loading.value = false;
87
+ loaded.value = false;
88
+ error.value = true;
89
+ }
90
+
91
+ globalSettings.updateDataVersion();
92
+ }
93
+
94
+ function show() {
95
+ showFeeds.value = true;
96
+ }
97
+
98
+ function hide() {
99
+ showFeeds.value = false;
100
+ }
101
+
102
+ const feeds = computedAsync(
103
+ async () => {
104
+ return await collections.getFeeds({collectionId: properties.collectionId});
105
+ },
106
+ [],
107
+ {lazy: true}
108
+ );
109
+ </script>
110
+
111
+ <style scoped>
112
+ .collection-feed-block:not(:last-child) {
113
+ border-bottom-width: 1px;
114
+ }
115
+ </style>
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <div class="flex">
3
+ <div class="flex-shrink-0 w-16 text-right pr-1">
4
+ <a
5
+ href="#"
6
+ v-if="!subscribed"
7
+ @click.prevent.stop="feedsStore.subscribe(feed.url)"
8
+ class="text-blue-700"
9
+ >subscribe</a
10
+ >
11
+
12
+ <span
13
+ v-else
14
+ class="text-green-700 cursor-default"
15
+ >subscribed</span
16
+ >
17
+ </div>
18
+
19
+ <div class="flex-shrink-0 w-8 text-right pr-1">
20
+ <favicon-element
21
+ :url="feed.url"
22
+ class="w-4 h-4 align-text-bottom mx-1 inline" />
23
+ </div>
24
+
25
+ <div class="flex-grow">
26
+ <strong>{{ feed.title }}</strong>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="flex">
31
+ <div class="flex-shrink-0 w-16 pr-1"> </div>
32
+
33
+ <div class="max-w-3xl flex-1 bg-slate-50 border-2 rounded p-4">
34
+ <p>{{ feed.description }}</p>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script lang="ts" setup>
40
+ import {computed, ref} from "vue";
41
+ import type * as t from "@/logic/types";
42
+ import * as e from "@/logic/enums";
43
+ import * as api from "@/logic/api";
44
+ import {computedAsync} from "@vueuse/core";
45
+ import DOMPurify from "dompurify";
46
+ import {useEntriesStore} from "@/stores/entries";
47
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
48
+ import {useCollectionsStore} from "@/stores/collections";
49
+ import {useFeedsStore} from "@/stores/feeds";
50
+
51
+ const feedsStore = useFeedsStore();
52
+
53
+ const properties = defineProps<{
54
+ feed: t.CollectionFeedInfo;
55
+ }>();
56
+
57
+ const globalSettings = useGlobalSettingsStore();
58
+
59
+ const subscribed = computed(() => properties.feed.id in feedsStore.feeds);
60
+ </script>
61
+
62
+ <style scoped></style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="ffun-info-attention">
3
+ <h3>It looks like there's nothing to read!</h3>
4
+ <p
5
+ >But don't worry — we've prepared some ready-to-use thematic collections just for you. All the feeds in them are
6
+ fully tagged free of charge.</p
7
+ >
8
+ <p>Subscribe to some and enjoy the full power of Feeds Fun!</p>
9
+ <collections-block />
10
+ </div>
11
+ </template>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div>
3
+ <p
4
+ v-if="loading"
5
+ class="ffun-info-attention"
6
+ >Subscribing...</p
7
+ >
8
+
9
+ <p
10
+ v-if="loaded"
11
+ class="ffun-info-good"
12
+ >Feeds added!</p
13
+ >
14
+
15
+ <p
16
+ v-if="error"
17
+ class="ffun-info-bad"
18
+ >Unknown error occurred! Please, try later.</p
19
+ >
20
+ </div>
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import {computed, ref} from "vue";
25
+ import type * as t from "@/logic/types";
26
+ import * as e from "@/logic/enums";
27
+ import * as api from "@/logic/api";
28
+ import {computedAsync} from "@vueuse/core";
29
+ import DOMPurify from "dompurify";
30
+ import {useEntriesStore} from "@/stores/entries";
31
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
32
+ import {useCollectionsStore} from "@/stores/collections";
33
+
34
+ const properties = defineProps<{
35
+ loading: boolean;
36
+ loaded: boolean;
37
+ error: boolean;
38
+ }>();
39
+ </script>
40
+
41
+ <style scoped></style>
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <div class="ffun-info-attention">
3
+ <h3>Check your feed URLs</h3>
4
+
5
+ <p
6
+ >Websites like
7
+ <a
8
+ href="https://reddit.com"
9
+ target="_blank"
10
+ >Reddit</a
11
+ >
12
+ or
13
+ <a
14
+ href="https://arxiv.org"
15
+ target="_blank"
16
+ >arXiv</a
17
+ >
18
+ provide multiple feeds by:</p
19
+ >
20
+
21
+ <ul>
22
+ <li
23
+ >Customizing news categories lists in URL (like
24
+ <code class="inline-block">https://site.com/feed/rss?category1+category2</code>) instead of using separate
25
+ URLs.</li
26
+ >
27
+ <li
28
+ >Supporting different feed formats (like <code class="inline-block">https://site.com/feed/rss</code> and
29
+ <code class="inline-block">https://site.com/feed/atom</code>).</li
30
+ >
31
+ </ul>
32
+
33
+ <p
34
+ >This can lead to the same news item appearing in multiple feeds, not being recognized as news from collections,
35
+ and being processed with your API key. We do our best to detect such news, but there are always exceptions.</p
36
+ >
37
+
38
+ <p><strong>To get the most from free tagging in collections:</strong></p>
39
+
40
+ <ul>
41
+ <li>Use specific, granular feed URLs.</li>
42
+ <li
43
+ >Add feeds directly from
44
+ <a
45
+ class="ffun-normal-link"
46
+ href="#"
47
+ @click.prevent="goToCollections()"
48
+ >collections</a
49
+ >
50
+ instead of using URLs from sites.</li
51
+ >
52
+ </ul>
53
+
54
+ <user-setting-for-notification
55
+ class=""
56
+ kind="hide_message_check_your_feed_urls"
57
+ button-text="Hide this message" />
58
+ </div>
59
+ </template>
60
+
61
+ <script lang="ts" setup>
62
+ import {useRouter} from "vue-router";
63
+ import * as e from "@/logic/enums";
64
+
65
+ const router = useRouter();
66
+
67
+ function goToCollections() {
68
+ router.push({name: e.MainPanelMode.Collections, params: {}});
69
+ }
70
+ </script>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="ffun-info-attention">
3
+ <p>
4
+ Please
5
+ <a
6
+ href="#"
7
+ @click.prevent="goToSettings()"
8
+ >set up</a
9
+ >
10
+ your own OpenAI and/or Gemini API keys to tag your personal feeds.
11
+ </p>
12
+
13
+ <p>
14
+ Remember, Feeds Fun provides tags for feeds from
15
+ <a
16
+ href="#"
17
+ @click.prevent="goToCollections()"
18
+ >collections</a
19
+ >
20
+ for free. You do not need to set up your own API keys to have tags in news from collections.
21
+ </p>
22
+
23
+ <user-setting-for-notification
24
+ kind="hide_message_about_setting_up_key"
25
+ button-text="Hide this message" />
26
+ </div>
27
+ </template>
28
+
29
+ <script lang="ts" setup>
30
+ import {useRouter} from "vue-router";
31
+ import * as e from "@/logic/enums";
32
+
33
+ const router = useRouter();
34
+
35
+ function goToCollections() {
36
+ router.push({name: e.MainPanelMode.Collections, params: {}});
37
+ }
38
+
39
+ function goToSettings() {
40
+ router.push({name: e.MainPanelMode.Settings, params: {}});
41
+ }
42
+ </script>