feeds-fun 0.0.4
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/.eslintrc.cjs +15 -0
- package/.prettierrc.json +13 -0
- package/.vscode/extensions.json +3 -0
- package/README.md +52 -0
- package/env.d.ts +1 -0
- package/index.html +13 -0
- package/package.json +50 -0
- package/public/favicon.ico +0 -0
- package/src/App.vue +33 -0
- package/src/components/ConfigFlag.vue +22 -0
- package/src/components/ConfigSelector.vue +25 -0
- package/src/components/DiscoveryForm.vue +81 -0
- package/src/components/EntriesList.vue +51 -0
- package/src/components/EntryForList.vue +156 -0
- package/src/components/EntryInfo.vue +23 -0
- package/src/components/FeedForList.vue +115 -0
- package/src/components/FeedInfo.vue +35 -0
- package/src/components/FeedsCollections.vue +53 -0
- package/src/components/FeedsList.vue +27 -0
- package/src/components/FfunGithubButtons.vue +22 -0
- package/src/components/FfunTag.vue +95 -0
- package/src/components/OPMLUpload.vue +46 -0
- package/src/components/OpenaiTokensUsage.vue +61 -0
- package/src/components/RuleConstructor.vue +56 -0
- package/src/components/RuleScoreUpdater.vue +33 -0
- package/src/components/RulesList.vue +52 -0
- package/src/components/SimplePagination.vue +81 -0
- package/src/components/SupertokensLogin.vue +118 -0
- package/src/components/TagsFilter.vue +130 -0
- package/src/components/TagsFilterElement.vue +89 -0
- package/src/components/TagsList.vue +125 -0
- package/src/components/UserSetting.vue +129 -0
- package/src/inputs/Marker.vue +70 -0
- package/src/inputs/ScoreSelector.vue +38 -0
- package/src/layouts/SidePanelLayout.vue +231 -0
- package/src/layouts/WideLayout.vue +44 -0
- package/src/logic/api.ts +253 -0
- package/src/logic/constants.ts +8 -0
- package/src/logic/enums.ts +92 -0
- package/src/logic/settings.ts +37 -0
- package/src/logic/timer.ts +25 -0
- package/src/logic/types.ts +371 -0
- package/src/logic/utils.ts +39 -0
- package/src/main.ts +145 -0
- package/src/router/index.ts +61 -0
- package/src/stores/entries.ts +217 -0
- package/src/stores/globalSettings.ts +74 -0
- package/src/stores/globalState.ts +23 -0
- package/src/stores/supertokens.ts +144 -0
- package/src/stores/tags.ts +54 -0
- package/src/values/DateTime.vue +27 -0
- package/src/values/FeedId.vue +22 -0
- package/src/values/Score.vue +42 -0
- package/src/values/URL.vue +25 -0
- package/src/views/AuthView.vue +66 -0
- package/src/views/CollectionsView.vue +23 -0
- package/src/views/DiscoveryView.vue +26 -0
- package/src/views/FeedsView.vue +124 -0
- package/src/views/MainView.vue +67 -0
- package/src/views/NewsView.vue +96 -0
- package/src/views/RulesView.vue +33 -0
- package/src/views/SettingsView.vue +81 -0
- package/tsconfig.app.json +12 -0
- package/tsconfig.json +14 -0
- package/tsconfig.node.json +8 -0
- package/tsconfig.vitest.json +9 -0
- package/vite.config.ts +26 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="container">
|
|
3
|
+
<div class="nav-panel">
|
|
4
|
+
<h2 style="margin-top: 0; margin-bottom: 0">
|
|
5
|
+
<slot name="main-header"></slot>
|
|
6
|
+
</h2>
|
|
7
|
+
|
|
8
|
+
<hr />
|
|
9
|
+
|
|
10
|
+
<ul class="config-menu">
|
|
11
|
+
<li
|
|
12
|
+
v-if="hasSideMenuItem(1)"
|
|
13
|
+
class="config-menu-item">
|
|
14
|
+
<slot name="side-menu-item-1"></slot>
|
|
15
|
+
</li>
|
|
16
|
+
|
|
17
|
+
<li
|
|
18
|
+
v-if="hasSideMenuItem(2)"
|
|
19
|
+
class="config-menu-item">
|
|
20
|
+
<slot name="side-menu-item-2"></slot>
|
|
21
|
+
</li>
|
|
22
|
+
|
|
23
|
+
<li
|
|
24
|
+
v-if="hasSideMenuItem(3)"
|
|
25
|
+
class="config-menu-item">
|
|
26
|
+
<slot name="side-menu-item-3"></slot>
|
|
27
|
+
</li>
|
|
28
|
+
|
|
29
|
+
<li
|
|
30
|
+
v-if="hasSideMenuItem(4)"
|
|
31
|
+
class="config-menu-item">
|
|
32
|
+
<slot name="side-menu-item-4"></slot>
|
|
33
|
+
</li>
|
|
34
|
+
|
|
35
|
+
<li
|
|
36
|
+
v-if="hasSideMenuItem(5)"
|
|
37
|
+
class="config-menu-item">
|
|
38
|
+
<slot name="side-menu-item-5"></slot>
|
|
39
|
+
</li>
|
|
40
|
+
</ul>
|
|
41
|
+
|
|
42
|
+
<hr v-if="reloadButton" />
|
|
43
|
+
|
|
44
|
+
<a
|
|
45
|
+
v-if="reloadButton"
|
|
46
|
+
href="#"
|
|
47
|
+
@click="globalSettings.dataVersion += 1"
|
|
48
|
+
>Reload</a
|
|
49
|
+
>
|
|
50
|
+
|
|
51
|
+
<hr v-if="hasSideFooter" />
|
|
52
|
+
|
|
53
|
+
<slot name="side-footer"></slot>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="main-content">
|
|
57
|
+
<header style="padding-top: 1rem; display: flex; align-items: center">
|
|
58
|
+
<div style="display: flex; align-items: center; margin-right: auto">
|
|
59
|
+
<ul style="list-style-type: none; margin: 0; padding: 0">
|
|
60
|
+
<li style="display: inline-block; margin-right: 0.5rem">
|
|
61
|
+
<a
|
|
62
|
+
href="#"
|
|
63
|
+
@click.prevent="router.push({name: 'main', params: {}})"
|
|
64
|
+
style="text-decoration: none"
|
|
65
|
+
>Home</a
|
|
66
|
+
>
|
|
67
|
+
</li>
|
|
68
|
+
|
|
69
|
+
<li
|
|
70
|
+
v-for="[mode, props] of e.MainPanelModeProperties"
|
|
71
|
+
:key="mode"
|
|
72
|
+
style="display: inline-block; margin-right: 0.5rem">
|
|
73
|
+
<a
|
|
74
|
+
v-if="globalSettings.mainPanelMode !== mode"
|
|
75
|
+
href="#"
|
|
76
|
+
@click.prevent="router.push({name: mode, params: {}})"
|
|
77
|
+
style="text-decoration: none">
|
|
78
|
+
{{ props.text }}
|
|
79
|
+
</a>
|
|
80
|
+
|
|
81
|
+
<span v-else>{{ props.text }}</span>
|
|
82
|
+
</li>
|
|
83
|
+
|
|
84
|
+
<li style="display: inline-block; margin-right: 0.5rem">
|
|
85
|
+
<a
|
|
86
|
+
href="/api/docs"
|
|
87
|
+
target="_blank"
|
|
88
|
+
style="text-decoration: none"
|
|
89
|
+
>API↗</a
|
|
90
|
+
>
|
|
91
|
+
</li>
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div style="display: flex; align-items: center; margin-left: 1rem">
|
|
96
|
+
<ffun-github-buttons
|
|
97
|
+
:repository="settings.githubRepo"
|
|
98
|
+
style="margin-top: 0.3rem" />
|
|
99
|
+
|
|
|
100
|
+
<a
|
|
101
|
+
href="#"
|
|
102
|
+
@click.prevent="logout()"
|
|
103
|
+
>logout</a
|
|
104
|
+
>
|
|
105
|
+
</div>
|
|
106
|
+
</header>
|
|
107
|
+
|
|
108
|
+
<hr />
|
|
109
|
+
|
|
110
|
+
<div
|
|
111
|
+
v-if="showApiKeyMessage"
|
|
112
|
+
style="border: 1px solid #ccc; padding: 1rem; margin-bottom: 1rem; background-color: #f0f8ff">
|
|
113
|
+
<p>
|
|
114
|
+
Because, for now, our service is free to use and OpenAI API costs money, we politely ask you to set up your
|
|
115
|
+
own OpenAI API key.
|
|
116
|
+
</p>
|
|
117
|
+
<p>
|
|
118
|
+
You can do it on the
|
|
119
|
+
<a
|
|
120
|
+
href="#"
|
|
121
|
+
@click.prevent="router.push({name: e.MainPanelMode.Settings, params: {}})"
|
|
122
|
+
>settings</a
|
|
123
|
+
>
|
|
124
|
+
page.
|
|
125
|
+
</p>
|
|
126
|
+
<user-setting kind="openai_hide_message_about_setting_up_key" />
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<main>
|
|
130
|
+
<slot></slot>
|
|
131
|
+
</main>
|
|
132
|
+
|
|
133
|
+
<footer>
|
|
134
|
+
<slot name="main-footer"></slot>
|
|
135
|
+
</footer>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</template>
|
|
139
|
+
|
|
140
|
+
<script lang="ts" setup>
|
|
141
|
+
import {ref, computed, useSlots, onMounted, watch, watchEffect} from "vue";
|
|
142
|
+
import {useRouter, RouterLink, RouterView} from "vue-router";
|
|
143
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
144
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
145
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
146
|
+
import {useSupertokens} from "@/stores/supertokens";
|
|
147
|
+
import * as e from "@/logic/enums";
|
|
148
|
+
import * as settings from "@/logic/settings";
|
|
149
|
+
|
|
150
|
+
const globalSettings = useGlobalSettingsStore();
|
|
151
|
+
const entriesStore = useEntriesStore();
|
|
152
|
+
const supertokens = useSupertokens();
|
|
153
|
+
const globalState = useGlobalState();
|
|
154
|
+
|
|
155
|
+
const router = useRouter();
|
|
156
|
+
const slots = useSlots();
|
|
157
|
+
|
|
158
|
+
const properties = withDefaults(defineProps<{reloadButton?: boolean; loginRequired?: boolean}>(), {
|
|
159
|
+
reloadButton: true,
|
|
160
|
+
loginRequired: true
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
async function logout() {
|
|
164
|
+
if (settings.authMode === settings.AuthMode.SingleUser) {
|
|
165
|
+
alert("You can't logout in single user mode");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await supertokens.logout();
|
|
170
|
+
router.push({name: "main", params: {}});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hasSideFooter = computed(() => {
|
|
174
|
+
return !!slots["side-footer"];
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
function hasSideMenuItem(index: number) {
|
|
178
|
+
return !!slots[`side-menu-item-${index}`];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const showApiKeyMessage = computed(() => {
|
|
182
|
+
return (
|
|
183
|
+
globalSettings.userSettings &&
|
|
184
|
+
!globalSettings.userSettings.openai_api_key.value &&
|
|
185
|
+
!globalSettings.userSettings.openai_hide_message_about_setting_up_key.value
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
watchEffect(() => {
|
|
190
|
+
if (!properties.loginRequired) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (globalState.isLoggedIn === null) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!globalState.isLoggedIn) {
|
|
199
|
+
router.push({name: "main", params: {}});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<style scoped>
|
|
205
|
+
.container {
|
|
206
|
+
display: flex;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.nav-panel {
|
|
210
|
+
width: 11rem;
|
|
211
|
+
flex-shrink: 0;
|
|
212
|
+
background-color: #f0f0f0;
|
|
213
|
+
padding: 1rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.main-content {
|
|
217
|
+
flex-grow: 1;
|
|
218
|
+
padding: 1rem;
|
|
219
|
+
padding-top: 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.config-menu {
|
|
223
|
+
list-style-type: none;
|
|
224
|
+
margin: 0;
|
|
225
|
+
padding: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
:deep(.config-menu-item) {
|
|
229
|
+
margin-bottom: 1rem;
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="container">
|
|
3
|
+
<div class="main-content">
|
|
4
|
+
<header>
|
|
5
|
+
<h1>
|
|
6
|
+
<slot name="header"></slot>
|
|
7
|
+
</h1>
|
|
8
|
+
</header>
|
|
9
|
+
|
|
10
|
+
<main>
|
|
11
|
+
<slot></slot>
|
|
12
|
+
</main>
|
|
13
|
+
|
|
14
|
+
<footer>
|
|
15
|
+
<slot name="footer"></slot>
|
|
16
|
+
</footer>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup></script>
|
|
22
|
+
|
|
23
|
+
<style scoped>
|
|
24
|
+
.container {
|
|
25
|
+
display: flex;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
/* align-items: center; */
|
|
28
|
+
height: 100vh;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.main-content {
|
|
32
|
+
/* flex-grow: 1; */
|
|
33
|
+
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
justify-content: start;
|
|
37
|
+
align-items: center;
|
|
38
|
+
text-align: center;
|
|
39
|
+
width: 50%;
|
|
40
|
+
/* margin-bottom: 50vh; */
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
max-width: 960px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
package/src/logic/api.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import * as _ from "lodash";
|
|
2
|
+
import axios, {AxiosError} from "axios";
|
|
3
|
+
import * as t from "@/logic/types";
|
|
4
|
+
import type * as e from "@/logic/enums";
|
|
5
|
+
|
|
6
|
+
const ENTRY_POINT = "/api";
|
|
7
|
+
|
|
8
|
+
const API_GET_FEEDS = `${ENTRY_POINT}/get-feeds`;
|
|
9
|
+
const API_GET_LAST_ENTRIES = `${ENTRY_POINT}/get-last-entries`;
|
|
10
|
+
const API_GET_ENTRIES_BY_IDS = `${ENTRY_POINT}/get-entries-by-ids`;
|
|
11
|
+
const API_CREATE_RULE = `${ENTRY_POINT}/create-rule`;
|
|
12
|
+
|
|
13
|
+
const API_DELETE_RULE = `${ENTRY_POINT}/delete-rule`;
|
|
14
|
+
const API_UPDATE_RULE = `${ENTRY_POINT}/update-rule`;
|
|
15
|
+
const API_GET_RULES = `${ENTRY_POINT}/get-rules`;
|
|
16
|
+
const API_GET_SCORE_DETAILS = `${ENTRY_POINT}/get-score-details`;
|
|
17
|
+
const API_SET_MARKER = `${ENTRY_POINT}/set-marker`;
|
|
18
|
+
const API_REMOVE_MARKER = `${ENTRY_POINT}/remove-marker`;
|
|
19
|
+
const API_DISCOVER_FEEDS = `${ENTRY_POINT}/discover-feeds`;
|
|
20
|
+
const API_ADD_FEED = `${ENTRY_POINT}/add-feed`;
|
|
21
|
+
const API_ADD_OPML = `${ENTRY_POINT}/add-opml`;
|
|
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`;
|
|
25
|
+
const API_GET_TAGS_INFO = `${ENTRY_POINT}/get-tags-info`;
|
|
26
|
+
const API_GET_USER_SETTINGS = `${ENTRY_POINT}/get-user-settings`;
|
|
27
|
+
const API_SET_USER_SETTING = `${ENTRY_POINT}/set-user-setting`;
|
|
28
|
+
const API_GET_RESOURCE_HISTORY = `${ENTRY_POINT}/get-resource-history`;
|
|
29
|
+
const API_GET_INFO = `${ENTRY_POINT}/get-info`;
|
|
30
|
+
|
|
31
|
+
let _onSessionLost: () => void = () => {};
|
|
32
|
+
|
|
33
|
+
export function init({onSessionLost}: {onSessionLost: () => void}) {
|
|
34
|
+
_onSessionLost = onSessionLost;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function post({url, data}: {url: string; data: any}) {
|
|
38
|
+
try {
|
|
39
|
+
const response = await axios.post(url, data);
|
|
40
|
+
return response.data;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.log(error);
|
|
43
|
+
|
|
44
|
+
if (error instanceof Error && "response" in error) {
|
|
45
|
+
const axiosError = error as AxiosError;
|
|
46
|
+
if (axiosError.response && axiosError.response.status === 401) {
|
|
47
|
+
await _onSessionLost();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getFeeds() {
|
|
56
|
+
const response = await post({url: API_GET_FEEDS, data: {}});
|
|
57
|
+
|
|
58
|
+
const feeds = [];
|
|
59
|
+
|
|
60
|
+
for (let rawFeed of response.feeds) {
|
|
61
|
+
const feed = t.feedFromJSON(rawFeed);
|
|
62
|
+
feeds.push(feed);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return feeds;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function getLastEntries({period}: {period: number}) {
|
|
69
|
+
const response = await post({
|
|
70
|
+
url: API_GET_LAST_ENTRIES,
|
|
71
|
+
data: {period: period}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const entries = [];
|
|
75
|
+
|
|
76
|
+
for (let rawEntry of response.entries) {
|
|
77
|
+
const entry = t.entryFromJSON(rawEntry);
|
|
78
|
+
entries.push(entry);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return entries;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function getEntriesByIds({ids}: {ids: t.EntryId[]}) {
|
|
85
|
+
const response = await post({
|
|
86
|
+
url: API_GET_ENTRIES_BY_IDS,
|
|
87
|
+
data: {ids: ids}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const entries = [];
|
|
91
|
+
|
|
92
|
+
for (let rawEntry of response.entries) {
|
|
93
|
+
const entry = t.entryFromJSON(rawEntry);
|
|
94
|
+
entries.push(entry);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return entries;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function createRule({tags, score}: {tags: string[]; score: number}) {
|
|
101
|
+
const response = await post({
|
|
102
|
+
url: API_CREATE_RULE,
|
|
103
|
+
data: {tags: tags, score: score}
|
|
104
|
+
});
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function deleteRule({id}: {id: t.RuleId}) {
|
|
109
|
+
const response = await post({url: API_DELETE_RULE, data: {id: id}});
|
|
110
|
+
return response;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function updateRule({id, tags, score}: {id: t.RuleId; tags: string[]; score: number}) {
|
|
114
|
+
const response = await post({
|
|
115
|
+
url: API_UPDATE_RULE,
|
|
116
|
+
data: {id: id, tags: tags, score: score}
|
|
117
|
+
});
|
|
118
|
+
return response;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getRules() {
|
|
122
|
+
const response = await post({url: API_GET_RULES, data: {}});
|
|
123
|
+
|
|
124
|
+
const rules = [];
|
|
125
|
+
|
|
126
|
+
for (let rawRule of response.rules) {
|
|
127
|
+
const rule = t.ruleFromJSON(rawRule);
|
|
128
|
+
rules.push(rule);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return rules;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function getScoreDetails({entryId}: {entryId: t.EntryId}) {
|
|
135
|
+
const response = await post({
|
|
136
|
+
url: API_GET_SCORE_DETAILS,
|
|
137
|
+
data: {entryId: entryId}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const rules = [];
|
|
141
|
+
|
|
142
|
+
for (let rawRule of response.rules) {
|
|
143
|
+
const rule = t.ruleFromJSON(rawRule);
|
|
144
|
+
rules.push(rule);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return rules;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function setMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
|
|
151
|
+
await post({
|
|
152
|
+
url: API_SET_MARKER,
|
|
153
|
+
data: {entryId: entryId, marker: marker}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function removeMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
|
|
158
|
+
await post({
|
|
159
|
+
url: API_REMOVE_MARKER,
|
|
160
|
+
data: {entryId: entryId, marker: marker}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function discoverFeeds({url}: {url: string}) {
|
|
165
|
+
const response = await post({url: API_DISCOVER_FEEDS, data: {url: url}});
|
|
166
|
+
|
|
167
|
+
const feeds = [];
|
|
168
|
+
|
|
169
|
+
for (let rawFeed of response.feeds) {
|
|
170
|
+
const feed = t.feedInfoFromJSON(rawFeed);
|
|
171
|
+
feeds.push(feed);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return feeds;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function addFeed({url}: {url: string}) {
|
|
178
|
+
await post({url: API_ADD_FEED, data: {url: url}});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function addOPML({content}: {content: string}) {
|
|
182
|
+
await post({url: API_ADD_OPML, data: {content: content}});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function unsubscribe({feedId}: {feedId: t.FeedId}) {
|
|
186
|
+
await post({url: API_UNSUBSCRIBE, data: {feedId: feedId}});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function getFeedsCollections() {
|
|
190
|
+
const response = await post({url: API_GET_FEEDS_COLLECTIONS, data: {}});
|
|
191
|
+
|
|
192
|
+
return response.collections;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function subscribeToFeedsCollections({collectionsIds}: {collectionsIds: t.FeedsCollectionId[]}) {
|
|
196
|
+
await post({
|
|
197
|
+
url: API_SUBSCRIBE_TO_FEEDS_COLLECTIONS,
|
|
198
|
+
data: {collections: collectionsIds}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function getTagsInfo({uids}: {uids: string[]}) {
|
|
203
|
+
const response = await post({url: API_GET_TAGS_INFO, data: {uids: uids}});
|
|
204
|
+
|
|
205
|
+
const tags: {[key: string]: t.TagInfo} = {};
|
|
206
|
+
|
|
207
|
+
for (let uid in response.tags) {
|
|
208
|
+
const rawTag = response.tags[uid];
|
|
209
|
+
const tag = t.tagInfoFromJSON(rawTag);
|
|
210
|
+
tags[uid] = tag;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return tags;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function getUserSettings() {
|
|
217
|
+
const response = await post({url: API_GET_USER_SETTINGS, data: {}});
|
|
218
|
+
|
|
219
|
+
const settings: {[key: string]: t.UserSetting} = {};
|
|
220
|
+
|
|
221
|
+
for (let rawSetting of response.settings) {
|
|
222
|
+
const setting = t.userSettingFromJSON(rawSetting);
|
|
223
|
+
settings[setting.kind] = setting;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return settings;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function setUserSetting({kind, value}: {kind: string; value: string | number | boolean}) {
|
|
230
|
+
await post({url: API_SET_USER_SETTING, data: {kind: kind, value: value}});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function getResourceHistory({kind}: {kind: string}) {
|
|
234
|
+
const response = await post({
|
|
235
|
+
url: API_GET_RESOURCE_HISTORY,
|
|
236
|
+
data: {kind: kind}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const history = [];
|
|
240
|
+
|
|
241
|
+
for (let rawRecord of response.history) {
|
|
242
|
+
const record = t.resourceHistoryRecordFromJSON(rawRecord);
|
|
243
|
+
history.push(record);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return history;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function getInfo() {
|
|
250
|
+
const response = await post({url: API_GET_INFO, data: {}});
|
|
251
|
+
|
|
252
|
+
return response;
|
|
253
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as c from "@/logic/constants";
|
|
2
|
+
|
|
3
|
+
export type AnyEnum = {
|
|
4
|
+
[key in keyof any]: string | number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export enum MainPanelMode {
|
|
8
|
+
Entries = "entries",
|
|
9
|
+
Feeds = "feeds",
|
|
10
|
+
Rules = "rules",
|
|
11
|
+
Discovery = "discovery",
|
|
12
|
+
Collections = "collections",
|
|
13
|
+
Settings = "settings"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const MainPanelModeProperties = new Map<MainPanelMode, {text: string}>([
|
|
17
|
+
[MainPanelMode.Entries, {text: "News"}],
|
|
18
|
+
[MainPanelMode.Feeds, {text: "Feeds"}],
|
|
19
|
+
[MainPanelMode.Rules, {text: "Rules"}],
|
|
20
|
+
[MainPanelMode.Discovery, {text: "Discovery"}],
|
|
21
|
+
[MainPanelMode.Collections, {text: "Collections"}],
|
|
22
|
+
[MainPanelMode.Settings, {text: "Settings"}]
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export enum LastEntriesPeriod {
|
|
26
|
+
Hour1 = "hour1",
|
|
27
|
+
Hour3 = "hour3",
|
|
28
|
+
Hour6 = "hour6",
|
|
29
|
+
Hour12 = "hour12",
|
|
30
|
+
Day1 = "day1",
|
|
31
|
+
Day3 = "day3",
|
|
32
|
+
Week = "week",
|
|
33
|
+
Week2 = "week2",
|
|
34
|
+
Month1 = "month",
|
|
35
|
+
Month3 = "month3",
|
|
36
|
+
Month6 = "month6",
|
|
37
|
+
Year1 = "year1",
|
|
38
|
+
AllTime = "alltime"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Map preserves the order of the keys
|
|
42
|
+
export const LastEntriesPeriodProperties = new Map<LastEntriesPeriod, {text: string; seconds: number}>([
|
|
43
|
+
[LastEntriesPeriod.Hour1, {text: "1 hour", seconds: c.hour}],
|
|
44
|
+
[LastEntriesPeriod.Hour3, {text: "3 hours", seconds: 3 * c.hour}],
|
|
45
|
+
[LastEntriesPeriod.Hour6, {text: "6 hours", seconds: 6 * c.hour}],
|
|
46
|
+
[LastEntriesPeriod.Hour12, {text: "12 hours", seconds: 12 * c.hour}],
|
|
47
|
+
[LastEntriesPeriod.Day1, {text: "1 day", seconds: c.day}],
|
|
48
|
+
[LastEntriesPeriod.Day3, {text: "3 days", seconds: 3 * c.day}],
|
|
49
|
+
[LastEntriesPeriod.Week, {text: "1 week", seconds: c.week}],
|
|
50
|
+
[LastEntriesPeriod.Week2, {text: "2 weeks", seconds: 2 * c.week}],
|
|
51
|
+
[LastEntriesPeriod.Month1, {text: "1 month", seconds: c.month}],
|
|
52
|
+
[LastEntriesPeriod.Month3, {text: "3 months", seconds: 3 * c.month}],
|
|
53
|
+
[LastEntriesPeriod.Month6, {text: "6 months", seconds: 6 * c.month}],
|
|
54
|
+
[LastEntriesPeriod.Year1, {text: "1 year", seconds: c.year}],
|
|
55
|
+
[LastEntriesPeriod.AllTime, {text: "all time", seconds: c.infinity}]
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
export enum EntriesOrder {
|
|
59
|
+
Score = "score",
|
|
60
|
+
ScoreToZero = "score-to-zero",
|
|
61
|
+
Published = "published",
|
|
62
|
+
Cataloged = "cataloged"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const EntriesOrderProperties = new Map<EntriesOrder, {text: string; orderField: string; timeField: string}>([
|
|
66
|
+
[EntriesOrder.Score, {text: "score", orderField: "score", timeField: "catalogedAt"}],
|
|
67
|
+
[EntriesOrder.ScoreToZero, {text: "score ~ 0", orderField: "scoreToZero", timeField: "catalogedAt"}],
|
|
68
|
+
[EntriesOrder.Published, {text: "published", orderField: "publishedAt", timeField: "publishedAt"}],
|
|
69
|
+
[EntriesOrder.Cataloged, {text: "cataloged", orderField: "catalogedAt", timeField: "catalogedAt"}]
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
export enum Marker {
|
|
73
|
+
Read = "read"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const reverseMarker: {[key: string]: Marker} = {
|
|
77
|
+
read: Marker.Read
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export enum FeedsOrder {
|
|
81
|
+
Title = "title",
|
|
82
|
+
Url = "url",
|
|
83
|
+
Loaded = "loaded",
|
|
84
|
+
Linked = "linked"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const FeedsOrderProperties = new Map<FeedsOrder, {text: string; orderField: string; orderDirection: string}>([
|
|
88
|
+
[FeedsOrder.Title, {text: "title", orderField: "title", orderDirection: "asc"}],
|
|
89
|
+
[FeedsOrder.Url, {text: "url", orderField: "url", orderDirection: "asc"}],
|
|
90
|
+
[FeedsOrder.Loaded, {text: "loaded", orderField: "loadedAt", orderDirection: "desc"}],
|
|
91
|
+
[FeedsOrder.Linked, {text: "added", orderField: "linkedAt", orderDirection: "desc"}]
|
|
92
|
+
]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export enum AuthMode {
|
|
2
|
+
SingleUser = "single_user",
|
|
3
|
+
Supertokens = "supertokens"
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const appName = import.meta.env.VITE_FFUN_APP_NAME || "Feeds Fun";
|
|
7
|
+
export const appDomain = import.meta.env.VITE_FFUN_APP_DOMAIN || "localhost";
|
|
8
|
+
export const appPort = import.meta.env.VITE_FFUN_APP_PORT || "5173";
|
|
9
|
+
|
|
10
|
+
export const environment = import.meta.env.VITE_FFUN_ENVIRONMENT || "local";
|
|
11
|
+
|
|
12
|
+
export const authMode = import.meta.env.VITE_FFUN_AUTH_MODE || AuthMode.SingleUser;
|
|
13
|
+
export const authSupertokensApiBasePath = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_API_BASE_PATH || "/supertokens";
|
|
14
|
+
export const authSupertokensResendAfter = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_RESEND_AFTER || 60 * 1000;
|
|
15
|
+
|
|
16
|
+
export const sentryEnable = import.meta.env.VITE_FFUN_ENABLE_SENTRY == "True" || false;
|
|
17
|
+
|
|
18
|
+
export const sentryDsn = import.meta.env.VITE_FFUN_SENTRY_DSN || "not-secified";
|
|
19
|
+
export const sentrySampleRate = import.meta.env.VITE_FFUN_SENTRY_SAMPLE_RATE || 1.0;
|
|
20
|
+
|
|
21
|
+
export const githubRepo = import.meta.env.VITE_FFUN_GITHUB_REPO || "https://github.com/Tiendil/feeds.fun";
|
|
22
|
+
|
|
23
|
+
console.log("settings.appName", appName);
|
|
24
|
+
console.log("settings.appDomain", appDomain);
|
|
25
|
+
console.log("settings.appPort", appPort);
|
|
26
|
+
|
|
27
|
+
console.log("settings.environment", environment);
|
|
28
|
+
|
|
29
|
+
console.log("settings.authMode", authMode);
|
|
30
|
+
console.log("settings.authSupertokensApiBasePath", authSupertokensApiBasePath);
|
|
31
|
+
console.log("settings.authSupertokensResendAfter", authSupertokensResendAfter);
|
|
32
|
+
|
|
33
|
+
console.log("settings.sentryEnable", sentryEnable);
|
|
34
|
+
console.log("settings.sentryDsn", sentryDsn);
|
|
35
|
+
console.log("settings.sentrySampleRate", sentrySampleRate);
|
|
36
|
+
|
|
37
|
+
console.log("settings.githubRepo", githubRepo);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {setIntervalAsync, clearIntervalAsync} from "set-interval-async";
|
|
2
|
+
|
|
3
|
+
export class Timer {
|
|
4
|
+
_timer: any;
|
|
5
|
+
_refresher: any;
|
|
6
|
+
delay: number;
|
|
7
|
+
|
|
8
|
+
constructor(refresher: any, delay: number) {
|
|
9
|
+
this._timer = null;
|
|
10
|
+
this._refresher = refresher;
|
|
11
|
+
this.delay = delay;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async start() {
|
|
15
|
+
await this._refresher();
|
|
16
|
+
this._timer = setIntervalAsync(this._refresher, this.delay);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async stop() {
|
|
20
|
+
if (this._timer != null) {
|
|
21
|
+
await clearIntervalAsync(this._timer);
|
|
22
|
+
this._timer = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|