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.
- package/package.json +1 -1
- package/src/components/DiscoveryForm.vue +5 -2
- package/src/components/FeedForList.vue +19 -7
- package/src/components/{OpenaiTokensUsage.vue → TokensCost.vue} +19 -9
- package/src/components/UserSettingForNotification.vue +1 -1
- package/src/components/collections/Block.vue +97 -0
- package/src/components/collections/BlockItem.vue +45 -0
- package/src/components/collections/DetailedItem.vue +115 -0
- package/src/components/collections/FeedItem.vue +62 -0
- package/src/components/collections/Notification.vue +11 -0
- package/src/components/collections/SubscribingProgress.vue +41 -0
- package/src/components/collections/Warning.vue +70 -0
- package/src/components/notifications/ApiKey.vue +42 -0
- package/src/components/notifications/Block.vue +61 -0
- package/src/components/{NotificationCreateRuleHelp.vue → notifications/CreateRuleHelp.vue} +2 -2
- package/src/layouts/SidePanelLayout.vue +38 -7
- package/src/logic/api.ts +31 -7
- package/src/logic/settings.ts +6 -0
- package/src/logic/types.ts +108 -11
- package/src/main.ts +26 -12
- package/src/stores/collections.ts +53 -0
- package/src/stores/feeds.ts +48 -0
- package/src/style.css +20 -10
- package/src/views/CollectionsView.vue +21 -4
- package/src/views/DiscoveryView.vue +6 -0
- package/src/views/FeedsView.vue +11 -12
- package/src/views/MainView.vue +7 -0
- package/src/views/NewsView.vue +3 -2
- package/src/views/SettingsView.vue +29 -21
- package/src/components/FeedsCollections.vue +0 -102
- package/src/components/NotificationCollections.vue +0 -7
- package/src/components/NotificationOpenaiApiKey.vue +0 -28
- 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
|
|
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
|
|
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
|
|
75
|
+
>API</a
|
|
76
76
|
>
|
|
77
77
|
</li>
|
|
78
78
|
|
|
79
|
-
<li>
|
|
79
|
+
<li v-if="settings.blog">
|
|
80
80
|
<a
|
|
81
|
-
:href="settings.
|
|
81
|
+
:href="settings.blog"
|
|
82
82
|
target="_blank"
|
|
83
83
|
class="ffun-header-link"
|
|
84
84
|
style="text-decoration: none"
|
|
85
|
-
>
|
|
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
|
|
24
|
-
const
|
|
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
|
|
190
|
-
const response = await post({url:
|
|
190
|
+
export async function getCollections() {
|
|
191
|
+
const response = await post({url: API_GET_COLLECTIONS, data: {}});
|
|
191
192
|
|
|
192
|
-
|
|
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
|
|
219
|
+
export async function subscribeToCollections({collectionsIds}: {collectionsIds: t.CollectionId[]}) {
|
|
196
220
|
await post({
|
|
197
|
-
url:
|
|
221
|
+
url: API_SUBSCRIBE_TO_COLLECTIONS,
|
|
198
222
|
data: {collections: collectionsIds}
|
|
199
223
|
});
|
|
200
224
|
}
|
package/src/logic/settings.ts
CHANGED
|
@@ -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);
|
package/src/logic/types.ts
CHANGED
|
@@ -18,10 +18,10 @@ export function toRuleId(id: string): RuleId {
|
|
|
18
18
|
return id as RuleId;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type CollectionId = string & {readonly __brand: unique symbol};
|
|
22
22
|
|
|
23
|
-
export function
|
|
24
|
-
return id as
|
|
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 {
|
|
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
|
-
|
|
382
|
-
|
|
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
|
|
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("
|
|
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
|
|
52
|
+
@apply ffun-info-common bg-green-50;
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
.ffun-info-bad {
|
|
42
|
-
@apply ffun-info-common
|
|
56
|
+
@apply ffun-info-common bg-red-50;
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
.ffun-info-warning {
|
|
46
|
-
@apply ffun-info-common
|
|
60
|
+
@apply ffun-info-common bg-orange-50;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
.ffun-info-attention {
|
|
50
|
-
@apply ffun-info-common
|
|
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-
|
|
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
|
}
|