feeds-fun 1.21.5 → 1.22.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 -2
- package/src/App.vue +2 -2
- package/src/components/EntryForList.vue +3 -3
- package/src/components/page_footer/Footer.vue +4 -1
- package/src/components/page_header/ExternalLinks.vue +3 -4
- package/src/components/side_pannel/CollapseButton.vue +0 -1
- package/src/components/tags/FilterTag.vue +4 -4
- package/src/css/inputs.css +21 -0
- package/src/layouts/SidePanelLayout.vue +19 -28
- package/src/logic/api.ts +265 -158
- package/src/logic/enums.ts +10 -0
- package/src/logic/events.ts +20 -12
- package/src/logic/settings.ts +4 -23
- package/src/logic/types.ts +26 -0
- package/src/main.ts +1 -54
- package/src/router/index.ts +0 -6
- package/src/stores/entries.ts +4 -4
- package/src/stores/globalSettings.ts +3 -11
- package/src/stores/globalState.ts +107 -9
- package/src/values/SocialLink.vue +8 -4
- package/src/views/CRMView.vue +1 -1
- package/src/views/MainView.vue +51 -15
- package/src/views/PublicCollectionView.vue +8 -3
- package/src/views/SettingsView.vue +5 -6
- package/vite.config.ts +0 -13
- package/src/components/SupertokensLogin.vue +0 -141
- package/src/stores/supertokens.ts +0 -144
- package/src/views/AuthView.vue +0 -77
package/src/logic/settings.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
declare const __APP_VERSION__: string;
|
|
2
2
|
|
|
3
|
-
export enum AuthMode {
|
|
4
|
-
SingleUser = "single_user",
|
|
5
|
-
Supertokens = "supertokens"
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const appName = import.meta.env.VITE_FFUN_APP_NAME || "Feeds Fun";
|
|
9
|
-
export const appDomain = import.meta.env.VITE_FFUN_APP_DOMAIN || "localhost";
|
|
10
|
-
export const appPort = import.meta.env.VITE_FFUN_APP_PORT || "5173";
|
|
11
|
-
|
|
12
|
-
export const environment = import.meta.env.VITE_FFUN_ENVIRONMENT || "local";
|
|
13
3
|
export const version = __APP_VERSION__;
|
|
14
4
|
|
|
15
|
-
export const
|
|
16
|
-
export const authSupertokensApiBasePath = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_API_BASE_PATH || "/supertokens";
|
|
17
|
-
export const authSupertokensResendAfter = import.meta.env.VITE_FFUN_AUTH_SUPERTOKENS_RESEND_AFTER || 60 * 1000;
|
|
5
|
+
export const authRefreshInterval = import.meta.env.VITE_FFUN_AUTH_REFRESH_INTERVAL || 10 * 60 * 1000;
|
|
18
6
|
|
|
19
7
|
export const blog = import.meta.env.VITE_FFUN_BLOG || "https://blog.feeds.fun";
|
|
8
|
+
export const roadmap = import.meta.env.VITE_FFUN_ROADMAP || "https://github.com/users/Tiendil/projects/1";
|
|
20
9
|
export const githubRepo = import.meta.env.VITE_FFUN_GITHUB_REPO || "https://github.com/Tiendil/feeds.fun";
|
|
21
10
|
export const discordInvite = import.meta.env.VITE_FFUN_DISCORD_INVITE || "https://discord.gg/C5RVusHQXy";
|
|
22
11
|
export const redditSubreddit = import.meta.env.VITE_FFUN_REDDIT_SUBREDDIT || "https://www.reddit.com/r/feedsfun/";
|
|
@@ -34,18 +23,12 @@ export const crmPrivacy = import.meta.env.VITE_FFUN_CRM_PRIVACY || null;
|
|
|
34
23
|
|
|
35
24
|
export const hasCollections = import.meta.env.VITE_FFUN_HAS_COLLECTIONS == "true" || false;
|
|
36
25
|
|
|
37
|
-
console.log("settings.appName", appName);
|
|
38
|
-
console.log("settings.appDomain", appDomain);
|
|
39
|
-
console.log("settings.appPort", appPort);
|
|
40
|
-
|
|
41
|
-
console.log("settings.environment", environment);
|
|
42
26
|
console.log("settings.version", version);
|
|
43
27
|
|
|
44
|
-
console.log("settings.
|
|
45
|
-
console.log("settings.authSupertokensApiBasePath", authSupertokensApiBasePath);
|
|
46
|
-
console.log("settings.authSupertokensResendAfter", authSupertokensResendAfter);
|
|
28
|
+
console.log("settings.authRefreshInterval", authRefreshInterval);
|
|
47
29
|
|
|
48
30
|
console.log("settings.blog", blog);
|
|
31
|
+
console.log("settings.roadmap", roadmap);
|
|
49
32
|
console.log("settings.githubRepo", githubRepo);
|
|
50
33
|
console.log("settings.discordInvite", discordInvite);
|
|
51
34
|
console.log("settings.redditSubreddit", redditSubreddit);
|
|
@@ -62,5 +45,3 @@ console.log("settings.crmTerms", crmTerms ? "set" : "not set");
|
|
|
62
45
|
console.log("settings.crmPrivacy", crmPrivacy ? "set" : "not set");
|
|
63
46
|
|
|
64
47
|
console.log("settings.hasCollections", hasCollections);
|
|
65
|
-
|
|
66
|
-
export const isSingleUserMode = authMode === AuthMode.SingleUser;
|
package/src/logic/types.ts
CHANGED
|
@@ -531,3 +531,29 @@ export class ApiMessage {
|
|
|
531
531
|
export function apiMessageFromJSON({type, code, message}: {type: string; code: string; message: string}): ApiMessage {
|
|
532
532
|
return new ApiMessage({type, code, message});
|
|
533
533
|
}
|
|
534
|
+
|
|
535
|
+
export class stateInfo {
|
|
536
|
+
readonly version: string;
|
|
537
|
+
readonly singleUserMode: boolean;
|
|
538
|
+
|
|
539
|
+
constructor({version, singleUserMode}: {version: string; singleUserMode: boolean}) {
|
|
540
|
+
this.version = version;
|
|
541
|
+
this.singleUserMode = singleUserMode;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function stateInfoFromJSON({version, singleUserMode}: {version: string; singleUserMode: boolean}): stateInfo {
|
|
546
|
+
return new stateInfo({version, singleUserMode});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export class userInfo {
|
|
550
|
+
readonly userId: string;
|
|
551
|
+
|
|
552
|
+
constructor({userId}: {userId: string}) {
|
|
553
|
+
this.userId = userId;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function userInfoFromJSON({userId}: {userId: string}): userInfo {
|
|
558
|
+
return new userInfo({userId});
|
|
559
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -19,7 +19,6 @@ import DiscoveryForm from "./components/DiscoveryForm.vue";
|
|
|
19
19
|
import FeedInfo from "./components/FeedInfo.vue";
|
|
20
20
|
import OpmlUpload from "./components/OPMLUpload.vue";
|
|
21
21
|
import FeedForList from "./components/FeedForList.vue";
|
|
22
|
-
import SupertokensLogin from "./components/SupertokensLogin.vue";
|
|
23
22
|
import SimplePagination from "./components/SimplePagination.vue";
|
|
24
23
|
import UserSetting from "./components/UserSetting.vue";
|
|
25
24
|
import TokensCost from "./components/TokensCost.vue";
|
|
@@ -78,8 +77,6 @@ import SidePanelCollapseButton from "./components/side_pannel/CollapseButton.vue
|
|
|
78
77
|
import WideLayout from "./layouts/WideLayout.vue";
|
|
79
78
|
import SidePanelLayout from "./layouts/SidePanelLayout.vue";
|
|
80
79
|
|
|
81
|
-
import {useSupertokens} from "@/stores/supertokens";
|
|
82
|
-
|
|
83
80
|
import VueCountdown from "@chenfengyuan/vue-countdown";
|
|
84
81
|
|
|
85
82
|
const app = createApp(App);
|
|
@@ -95,7 +92,6 @@ app.component("DiscoveryForm", DiscoveryForm);
|
|
|
95
92
|
app.component("FeedInfo", FeedInfo);
|
|
96
93
|
app.component("OpmlUpload", OpmlUpload);
|
|
97
94
|
app.component("FeedForList", FeedForList);
|
|
98
|
-
app.component("SupertokensLogin", SupertokensLogin);
|
|
99
95
|
app.component("SimplePagination", SimplePagination);
|
|
100
96
|
app.component("UserSetting", UserSetting);
|
|
101
97
|
app.component("TokensCost", TokensCost);
|
|
@@ -158,55 +154,6 @@ app.component("vue-countdown", VueCountdown);
|
|
|
158
154
|
|
|
159
155
|
app.use(createPinia());
|
|
160
156
|
app.use(router);
|
|
161
|
-
|
|
162
|
-
if (!settings.isSingleUserMode) {
|
|
163
|
-
app.use(CookieConsent.plugin, CookieConsent.defaultConfig);
|
|
164
|
-
}
|
|
157
|
+
app.use(CookieConsent.plugin, CookieConsent.defaultConfig);
|
|
165
158
|
|
|
166
159
|
app.mount("#app");
|
|
167
|
-
|
|
168
|
-
import * as api from "@/logic/api";
|
|
169
|
-
import * as settings from "@/logic/settings";
|
|
170
|
-
|
|
171
|
-
/////////////////////
|
|
172
|
-
// supertokens
|
|
173
|
-
/////////////////////
|
|
174
|
-
|
|
175
|
-
// must be copy of smart_url from backend
|
|
176
|
-
function smartUrl(domain: string, port: number) {
|
|
177
|
-
if (port == 80) {
|
|
178
|
-
return `http://${domain}`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (port == 443) {
|
|
182
|
-
return `https://${domain}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return `http://${domain}:${port}`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
let supertokens: ReturnType<typeof useSupertokens> | null = null;
|
|
189
|
-
|
|
190
|
-
if (settings.authMode === settings.AuthMode.Supertokens) {
|
|
191
|
-
supertokens = useSupertokens();
|
|
192
|
-
|
|
193
|
-
supertokens.init({
|
|
194
|
-
apiDomain: smartUrl(settings.appDomain, settings.appPort),
|
|
195
|
-
apiBasePath: settings.authSupertokensApiBasePath,
|
|
196
|
-
appName: settings.appName,
|
|
197
|
-
resendAfter: settings.authSupertokensResendAfter
|
|
198
|
-
});
|
|
199
|
-
} else if (settings.authMode === settings.AuthMode.SingleUser) {
|
|
200
|
-
} else {
|
|
201
|
-
throw `Unknown auth mode: ${settings.authMode}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async function onSessionLost() {
|
|
205
|
-
if (supertokens !== null) {
|
|
206
|
-
await supertokens.logout();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
router.push({name: "main", params: {}});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
api.init({onSessionLost: onSessionLost});
|
package/src/router/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {createRouter, createWebHistory} from "vue-router";
|
|
2
2
|
import MainView from "../views/MainView.vue";
|
|
3
|
-
import AuthView from "../views/AuthView.vue";
|
|
4
3
|
import FeedsView from "../views/FeedsView.vue";
|
|
5
4
|
import NewsView from "../views/NewsView.vue";
|
|
6
5
|
import RulesView from "../views/RulesView.vue";
|
|
@@ -23,11 +22,6 @@ const router = createRouter({
|
|
|
23
22
|
name: "main",
|
|
24
23
|
component: MainView
|
|
25
24
|
},
|
|
26
|
-
{
|
|
27
|
-
path: "/auth",
|
|
28
|
-
name: "auth",
|
|
29
|
-
component: AuthView
|
|
30
|
-
},
|
|
31
25
|
{
|
|
32
26
|
path: "/feeds",
|
|
33
27
|
name: e.MainPanelMode.Feeds,
|
package/src/stores/entries.ts
CHANGED
|
@@ -53,7 +53,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const readyToLoadNews = computed(() => {
|
|
56
|
-
return (globalSettings.userSettingsPresent || !globalState.
|
|
56
|
+
return (globalSettings.userSettingsPresent || !globalState.loginConfirmed) && mode.value !== null;
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
// Public collections uses fixed sorting order
|
|
@@ -248,7 +248,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
248
248
|
|
|
249
249
|
// This method may be called from public access pages, like public collections
|
|
250
250
|
// In such case user may be not logged in and we should not send API requests
|
|
251
|
-
if (globalState.
|
|
251
|
+
if (globalState.loginConfirmed) {
|
|
252
252
|
await api.setMarker({entryId: entryId, marker: marker});
|
|
253
253
|
}
|
|
254
254
|
}
|
|
@@ -267,7 +267,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
267
267
|
|
|
268
268
|
// This method may be called from public access pages, like public collections
|
|
269
269
|
// In such case user may be not logged in and we should not send API requests
|
|
270
|
-
if (globalState.
|
|
270
|
+
if (globalState.loginConfirmed) {
|
|
271
271
|
await api.removeMarker({entryId: entryId, marker: marker});
|
|
272
272
|
}
|
|
273
273
|
}
|
|
@@ -284,7 +284,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
events.newsBodyOpened({entryId: entryId, view: view});
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
function hideEntry({entryId}: {entryId: t.EntryId}) {
|
|
@@ -19,19 +19,12 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
19
19
|
dataVersion.value++;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// TODO: We may want to remove this API and move user_id to the user settings API
|
|
23
|
-
const info = computedAsync(async () => {
|
|
24
|
-
globalState.isLoggedIn;
|
|
25
|
-
dataVersion.value;
|
|
26
|
-
return await api.getInfo();
|
|
27
|
-
}, null);
|
|
28
|
-
|
|
29
22
|
///////////////////////////////////////////////////////////
|
|
30
23
|
// Functionality for interaction with backend side settings
|
|
31
24
|
///////////////////////////////////////////////////////////
|
|
32
25
|
|
|
33
26
|
const _userSettings = computedAsync(async () => {
|
|
34
|
-
if (!globalState.
|
|
27
|
+
if (!globalState.loginConfirmed) {
|
|
35
28
|
return null;
|
|
36
29
|
}
|
|
37
30
|
|
|
@@ -87,7 +80,7 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
87
80
|
function setUserSettings(kind: string, newValue: t.UserSettingsValue) {
|
|
88
81
|
settingsOverrides.value[kind] = newValue;
|
|
89
82
|
|
|
90
|
-
if (globalState.
|
|
83
|
+
if (globalState.loginConfirmed) {
|
|
91
84
|
_backgroundSetUserSetting(kind, newValue);
|
|
92
85
|
}
|
|
93
86
|
|
|
@@ -108,7 +101,7 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
108
101
|
return settingsOverrides.value[kind];
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
if (!globalState.
|
|
104
|
+
if (!globalState.loginConfirmed) {
|
|
112
105
|
return defaultValue;
|
|
113
106
|
}
|
|
114
107
|
|
|
@@ -223,7 +216,6 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
223
216
|
updateDataVersion,
|
|
224
217
|
showFeedsDescriptions,
|
|
225
218
|
userSettingsPresent,
|
|
226
|
-
info,
|
|
227
219
|
feedsOrder,
|
|
228
220
|
failedFeedsFirst,
|
|
229
221
|
rulesOrder,
|
|
@@ -1,23 +1,121 @@
|
|
|
1
1
|
import {ref, watch, computed} from "vue";
|
|
2
|
-
import {useRouter} from "vue-router";
|
|
2
|
+
import {useRouter, useRoute} from "vue-router";
|
|
3
3
|
import {defineStore} from "pinia";
|
|
4
|
-
import {
|
|
5
|
-
import * as settings from "@/logic/settings";
|
|
4
|
+
import {computedAsync, useBroadcastChannel} from "@vueuse/core";
|
|
6
5
|
|
|
6
|
+
import {Timer} from "@/logic/timer";
|
|
7
|
+
import * as settings from "@/logic/settings";
|
|
8
|
+
import * as api from "@/logic/api";
|
|
7
9
|
import * as e from "@/logic/enums";
|
|
8
10
|
|
|
11
|
+
const GLOBAL_BROADCAST_CHANNEL_ID = "ffun-global-event-channel";
|
|
12
|
+
const GLOBAL_BROADCAST_EVENT_LOGOUT_COMPLETED = "logoutCompleted";
|
|
13
|
+
|
|
14
|
+
const LOGGED_OUT_QUERY_MARKER = "logged-out";
|
|
15
|
+
|
|
9
16
|
export const useGlobalState = defineStore("globalState", () => {
|
|
10
|
-
const
|
|
17
|
+
const router = useRouter();
|
|
18
|
+
const route = useRoute();
|
|
19
|
+
|
|
20
|
+
const infoRefreshMarker = ref(0);
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
function refreshAuthState() {
|
|
23
|
+
infoRefreshMarker.value++;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
////////////////////////////////
|
|
27
|
+
// sync login state between tabs
|
|
28
|
+
////////////////////////////////
|
|
29
|
+
const globalChannel = useBroadcastChannel<any, any>({name: GLOBAL_BROADCAST_CHANNEL_ID});
|
|
30
|
+
|
|
31
|
+
watch(globalChannel.data, (event) => {
|
|
32
|
+
if (event.type === GLOBAL_BROADCAST_EVENT_LOGOUT_COMPLETED) {
|
|
33
|
+
refreshAuthState();
|
|
15
34
|
}
|
|
35
|
+
});
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
// check if the marker is in the URL (means that user has just redirected after logout)
|
|
38
|
+
watch(route, (r) => {
|
|
39
|
+
if (!(LOGGED_OUT_QUERY_MARKER in r.query)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
globalChannel.post({type: GLOBAL_BROADCAST_EVENT_LOGOUT_COMPLETED});
|
|
44
|
+
|
|
45
|
+
// remove the marker from the URL
|
|
46
|
+
const query = {...r.query};
|
|
47
|
+
delete query[LOGGED_OUT_QUERY_MARKER];
|
|
48
|
+
router.replace({query});
|
|
49
|
+
});
|
|
50
|
+
////////////////////////////////
|
|
51
|
+
|
|
52
|
+
// Periodic check auth state by timer
|
|
53
|
+
const authStateRefresher = new Timer(refreshAuthState, settings.authRefreshInterval);
|
|
54
|
+
|
|
55
|
+
// Check auth state on the particular events
|
|
56
|
+
// do not check "focus" and "visibilitychange" — they are too noisy
|
|
57
|
+
window.addEventListener("online", refreshAuthState);
|
|
58
|
+
window.addEventListener("pageshow", (event) => {
|
|
59
|
+
if (event.persisted) {
|
|
60
|
+
// BFCache restore (the user goes Back/Forward and the browser instantly revives a frozen snapshot).
|
|
61
|
+
refreshAuthState();
|
|
62
|
+
}
|
|
18
63
|
});
|
|
19
64
|
|
|
65
|
+
const info = computedAsync(async () => {
|
|
66
|
+
infoRefreshMarker.value;
|
|
67
|
+
return await api.getInfo();
|
|
68
|
+
}, null);
|
|
69
|
+
|
|
70
|
+
const userId = computedAsync(async () => {
|
|
71
|
+
infoRefreshMarker.value;
|
|
72
|
+
let userData = await api.getUser();
|
|
73
|
+
return userData ? userData.userId : null;
|
|
74
|
+
}, "unknown");
|
|
75
|
+
|
|
76
|
+
const isSingleUserMode = computed(() => {
|
|
77
|
+
return info.value ? info.value.singleUserMode : false;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const loginState = computed(() => {
|
|
81
|
+
if (userId.value === "unknown") {
|
|
82
|
+
return e.LoginState.Unknown;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return userId.value ? e.LoginState.LoggedIn : e.LoginState.LoggedOut;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const loginUnknown = computed(() => {
|
|
89
|
+
return loginState.value === e.LoginState.Unknown;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const loginConfirmed = computed(() => {
|
|
93
|
+
return loginState.value === e.LoginState.LoggedIn;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const logoutConfirmed = computed(() => {
|
|
97
|
+
return loginState.value === e.LoginState.LoggedOut;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
function logout() {
|
|
101
|
+
if (isSingleUserMode.value) {
|
|
102
|
+
// In single user mode the user is always "logged in"
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
api.logoutRedirect();
|
|
107
|
+
|
|
108
|
+
// TODO: we may want to notify other tabs about logout event
|
|
109
|
+
}
|
|
110
|
+
|
|
20
111
|
return {
|
|
21
|
-
|
|
112
|
+
userId,
|
|
113
|
+
isSingleUserMode,
|
|
114
|
+
loginState,
|
|
115
|
+
loginUnknown,
|
|
116
|
+
loginConfirmed,
|
|
117
|
+
logoutConfirmed,
|
|
118
|
+
refreshAuthState,
|
|
119
|
+
logout
|
|
22
120
|
};
|
|
23
121
|
});
|
|
@@ -25,13 +25,17 @@
|
|
|
25
25
|
import * as settings from "@/logic/settings";
|
|
26
26
|
import * as asserts from "@/logic/asserts";
|
|
27
27
|
|
|
28
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
29
|
+
|
|
30
|
+
const globalState = useGlobalState();
|
|
31
|
+
|
|
28
32
|
const links = {
|
|
29
|
-
|
|
33
|
+
roadmap: {
|
|
30
34
|
enabled: true,
|
|
31
|
-
url:
|
|
32
|
-
text: "
|
|
35
|
+
url: settings.roadmap,
|
|
36
|
+
text: "Roadmap",
|
|
33
37
|
icon: null,
|
|
34
|
-
eventType: "
|
|
38
|
+
eventType: "roadmap"
|
|
35
39
|
},
|
|
36
40
|
blog: {
|
|
37
41
|
enabled: settings.blog !== null,
|
package/src/views/CRMView.vue
CHANGED
package/src/views/MainView.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<wide-layout>
|
|
3
3
|
<div class="ffun-page-header">
|
|
4
4
|
<div class="ffun-page-header-center-block">
|
|
5
|
-
<page-header-external-links
|
|
5
|
+
<page-header-external-links />
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
@@ -10,18 +10,32 @@
|
|
|
10
10
|
|
|
11
11
|
<main-block>
|
|
12
12
|
<h1 class="m-0 text-5xl">Feeds Fun</h1>
|
|
13
|
-
<p class="mt-2 text-2xl">Transparent Personalized News</p>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
13
|
+
<p class="mt-2 text-2xl">Transparent & Personalized News</p>
|
|
14
|
+
|
|
15
|
+
<div class="h-12 grid grid-flow-col auto-cols-fr gap-3 w-max mx-auto">
|
|
16
|
+
<a
|
|
17
|
+
v-if="globalState.loginConfirmed"
|
|
18
|
+
class="ffun-main-auth-button ffun-go-to-feeds"
|
|
19
|
+
href="#"
|
|
20
|
+
@click.prevent="goToFeeds()">
|
|
21
|
+
Read your feed
|
|
22
|
+
</a>
|
|
23
|
+
|
|
24
|
+
<a
|
|
25
|
+
v-if="globalState.logoutConfirmed"
|
|
26
|
+
class="ffun-main-auth-button ffun-login"
|
|
27
|
+
href="#"
|
|
28
|
+
@click.prevent="goToLogin()"
|
|
29
|
+
>Sign in</a
|
|
30
|
+
>
|
|
31
|
+
|
|
32
|
+
<a
|
|
33
|
+
v-if="globalState.logoutConfirmed"
|
|
34
|
+
class="ffun-main-auth-button ffun-register"
|
|
35
|
+
href="#"
|
|
36
|
+
@click.prevent="goToJoin()"
|
|
37
|
+
>Join now</a
|
|
38
|
+
>
|
|
25
39
|
</div>
|
|
26
40
|
</main-block>
|
|
27
41
|
|
|
@@ -163,7 +177,7 @@
|
|
|
163
177
|
|
|
164
178
|
<main-news-title
|
|
165
179
|
class="opacity-75"
|
|
166
|
-
title="Sci-fi novel about UFO in New
|
|
180
|
+
title="Sci-fi novel about UFO in New York"
|
|
167
181
|
:score="13" />
|
|
168
182
|
|
|
169
183
|
<div class="opacity-65 block justify-self-center">
|
|
@@ -222,16 +236,38 @@
|
|
|
222
236
|
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
223
237
|
import exampleImage from "@/assets/news-filtering-example.png";
|
|
224
238
|
import * as settings from "@/logic/settings";
|
|
239
|
+
import * as api from "@/logic/api";
|
|
240
|
+
import * as e from "@/logic/enums";
|
|
241
|
+
import * as events from "@/logic/events";
|
|
225
242
|
import {useRouter, RouterLink, RouterView} from "vue-router";
|
|
226
243
|
import {useCollectionsStore} from "@/stores/collections";
|
|
244
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
227
245
|
import * as t from "@/logic/types";
|
|
228
246
|
|
|
229
247
|
const router = useRouter();
|
|
248
|
+
const globalState = useGlobalState();
|
|
230
249
|
const collections = useCollectionsStore();
|
|
231
250
|
|
|
232
|
-
|
|
251
|
+
const eventsView = "main";
|
|
252
|
+
|
|
253
|
+
provide("eventsViewName", eventsView);
|
|
233
254
|
|
|
234
255
|
function publicCollectionHref(collectionSlug: t.CollectionSlug) {
|
|
235
256
|
return router.resolve({name: "public-collection", params: {collectionSlug: collectionSlug}}).href;
|
|
236
257
|
}
|
|
258
|
+
|
|
259
|
+
function goToFeeds() {
|
|
260
|
+
events.authButtonClicked({buttonType: "go_to_feeds", view: eventsView});
|
|
261
|
+
router.push({name: e.MainPanelMode.Entries, params: {}});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function goToLogin() {
|
|
265
|
+
events.authButtonClicked({buttonType: "login", view: eventsView});
|
|
266
|
+
api.redirectToLogin("/news");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function goToJoin() {
|
|
270
|
+
events.authButtonClicked({buttonType: "join", view: eventsView});
|
|
271
|
+
api.redirectToJoin("/news");
|
|
272
|
+
}
|
|
237
273
|
</script>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
<tags-filter
|
|
52
52
|
:tags="tagsCount"
|
|
53
53
|
:show-create-rule="false"
|
|
54
|
-
:show-registration-invitation="
|
|
54
|
+
:show-registration-invitation="showRegistrationInvitation" />
|
|
55
55
|
</template>
|
|
56
56
|
|
|
57
57
|
<template #main-header>
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<!-- because the user previously selected tags that have no common news with the tags in the block -->
|
|
68
68
|
<!-- That effect is possible only if the user has already interacted with the ags filter => should not be a problem -->
|
|
69
69
|
<collections-public-intro
|
|
70
|
-
v-if="collection && !globalState.
|
|
70
|
+
v-if="collection && !globalState.loginConfirmed"
|
|
71
71
|
:collectionId="collection.id"
|
|
72
72
|
:tag1Uid="medianTag1"
|
|
73
73
|
:tag1Count="tagsCount[medianTag1] || 0"
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
:tag2Count="tagsCount[medianTag2] || 0" />
|
|
76
76
|
|
|
77
77
|
<div
|
|
78
|
-
v-if="collection && globalState.
|
|
78
|
+
v-if="collection && globalState.loginConfirmed"
|
|
79
79
|
class="ffun-info-good">
|
|
80
80
|
<p
|
|
81
81
|
>Welcome to curated <strong>{{ collection.name }}</strong> news collection!</p
|
|
@@ -126,6 +126,10 @@
|
|
|
126
126
|
|
|
127
127
|
const collectionSlug = computed(() => route.params.collectionSlug as t.CollectionSlug);
|
|
128
128
|
|
|
129
|
+
const showRegistrationInvitation = computed(() => {
|
|
130
|
+
return globalState.logoutConfirmed;
|
|
131
|
+
});
|
|
132
|
+
|
|
129
133
|
const collection = computed(() => {
|
|
130
134
|
if (!collectionSlug.value) {
|
|
131
135
|
return null;
|
|
@@ -183,6 +187,7 @@
|
|
|
183
187
|
let report = entriesStore.visibleEntries.slice();
|
|
184
188
|
|
|
185
189
|
report = tagsStates.value.filterByTags(report, (entryId) => entriesStore.entries[entryId].tags);
|
|
190
|
+
|
|
186
191
|
return report;
|
|
187
192
|
});
|
|
188
193
|
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
</div>
|
|
127
127
|
|
|
128
128
|
<div
|
|
129
|
-
v-if="!
|
|
129
|
+
v-if="!globalState.isSingleUserMode"
|
|
130
130
|
class="ffun-info-bad">
|
|
131
131
|
<button
|
|
132
132
|
@click.prevent="removeAccount()"
|
|
@@ -154,7 +154,9 @@
|
|
|
154
154
|
import * as settings from "@/logic/settings";
|
|
155
155
|
import {useRouter} from "vue-router";
|
|
156
156
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
157
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
157
158
|
|
|
159
|
+
const globalState = useGlobalState();
|
|
158
160
|
const globalSettings = useGlobalSettingsStore();
|
|
159
161
|
|
|
160
162
|
provide("eventsViewName", "settings");
|
|
@@ -166,11 +168,7 @@
|
|
|
166
168
|
}, null);
|
|
167
169
|
|
|
168
170
|
const userId = computed(() => {
|
|
169
|
-
|
|
170
|
-
return "—";
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return globalSettings.info.userId;
|
|
171
|
+
return globalState.userId == null ? "—" : globalState.userId;
|
|
174
172
|
});
|
|
175
173
|
|
|
176
174
|
const messagesSettings = [
|
|
@@ -188,6 +186,7 @@
|
|
|
188
186
|
function removeAccount() {
|
|
189
187
|
if (confirm("Are you sure you want to remove your account? THIS OPERATION IS NOT REVERSIBLE!")) {
|
|
190
188
|
api.removeUser();
|
|
189
|
+
globalState.logout();
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
|
package/vite.config.ts
CHANGED
|
@@ -16,17 +16,4 @@ export default defineConfig({
|
|
|
16
16
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
|
|
20
|
-
server: {
|
|
21
|
-
proxy: {
|
|
22
|
-
'/api': {
|
|
23
|
-
target: 'http://backend:8000/',
|
|
24
|
-
changeOrigin: true,
|
|
25
|
-
},
|
|
26
|
-
'/supertokens': {
|
|
27
|
-
target: 'http://backend:8000/',
|
|
28
|
-
changeOrigin: true,
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
19
|
})
|