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.
Files changed (68) hide show
  1. package/.eslintrc.cjs +15 -0
  2. package/.prettierrc.json +13 -0
  3. package/.vscode/extensions.json +3 -0
  4. package/README.md +52 -0
  5. package/env.d.ts +1 -0
  6. package/index.html +13 -0
  7. package/package.json +50 -0
  8. package/public/favicon.ico +0 -0
  9. package/src/App.vue +33 -0
  10. package/src/components/ConfigFlag.vue +22 -0
  11. package/src/components/ConfigSelector.vue +25 -0
  12. package/src/components/DiscoveryForm.vue +81 -0
  13. package/src/components/EntriesList.vue +51 -0
  14. package/src/components/EntryForList.vue +156 -0
  15. package/src/components/EntryInfo.vue +23 -0
  16. package/src/components/FeedForList.vue +115 -0
  17. package/src/components/FeedInfo.vue +35 -0
  18. package/src/components/FeedsCollections.vue +53 -0
  19. package/src/components/FeedsList.vue +27 -0
  20. package/src/components/FfunGithubButtons.vue +22 -0
  21. package/src/components/FfunTag.vue +95 -0
  22. package/src/components/OPMLUpload.vue +46 -0
  23. package/src/components/OpenaiTokensUsage.vue +61 -0
  24. package/src/components/RuleConstructor.vue +56 -0
  25. package/src/components/RuleScoreUpdater.vue +33 -0
  26. package/src/components/RulesList.vue +52 -0
  27. package/src/components/SimplePagination.vue +81 -0
  28. package/src/components/SupertokensLogin.vue +118 -0
  29. package/src/components/TagsFilter.vue +130 -0
  30. package/src/components/TagsFilterElement.vue +89 -0
  31. package/src/components/TagsList.vue +125 -0
  32. package/src/components/UserSetting.vue +129 -0
  33. package/src/inputs/Marker.vue +70 -0
  34. package/src/inputs/ScoreSelector.vue +38 -0
  35. package/src/layouts/SidePanelLayout.vue +231 -0
  36. package/src/layouts/WideLayout.vue +44 -0
  37. package/src/logic/api.ts +253 -0
  38. package/src/logic/constants.ts +8 -0
  39. package/src/logic/enums.ts +92 -0
  40. package/src/logic/settings.ts +37 -0
  41. package/src/logic/timer.ts +25 -0
  42. package/src/logic/types.ts +371 -0
  43. package/src/logic/utils.ts +39 -0
  44. package/src/main.ts +145 -0
  45. package/src/router/index.ts +61 -0
  46. package/src/stores/entries.ts +217 -0
  47. package/src/stores/globalSettings.ts +74 -0
  48. package/src/stores/globalState.ts +23 -0
  49. package/src/stores/supertokens.ts +144 -0
  50. package/src/stores/tags.ts +54 -0
  51. package/src/values/DateTime.vue +27 -0
  52. package/src/values/FeedId.vue +22 -0
  53. package/src/values/Score.vue +42 -0
  54. package/src/values/URL.vue +25 -0
  55. package/src/views/AuthView.vue +66 -0
  56. package/src/views/CollectionsView.vue +23 -0
  57. package/src/views/DiscoveryView.vue +26 -0
  58. package/src/views/FeedsView.vue +124 -0
  59. package/src/views/MainView.vue +67 -0
  60. package/src/views/NewsView.vue +96 -0
  61. package/src/views/RulesView.vue +33 -0
  62. package/src/views/SettingsView.vue +81 -0
  63. package/tsconfig.app.json +12 -0
  64. package/tsconfig.json +14 -0
  65. package/tsconfig.node.json +8 -0
  66. package/tsconfig.vitest.json +9 -0
  67. package/vite.config.ts +26 -0
  68. package/vitest.config.ts +15 -0
@@ -0,0 +1,217 @@
1
+ import {computed, ref, watch} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+ import _ from "lodash";
5
+ import * 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 useEntriesStore = defineStore("entriesStore", () => {
13
+ const globalSettings = useGlobalSettingsStore();
14
+
15
+ const entries = ref<{[key: t.EntryId]: t.Entry}>({});
16
+ const requestedEntries = ref<{[key: t.EntryId]: boolean}>({});
17
+
18
+ const requiredTags = ref<{[key: string]: boolean}>({});
19
+ const excludedTags = ref<{[key: string]: boolean}>({});
20
+
21
+ const firstTimeEntriesLoading = ref(true);
22
+
23
+ function registerEntry(entry: t.Entry) {
24
+ if (entry.id in entries.value) {
25
+ if (entry.body === null && entries.value[entry.id].body !== null) {
26
+ entry.body = entries.value[entry.id].body;
27
+ }
28
+ }
29
+
30
+ entries.value[entry.id] = entry;
31
+ }
32
+
33
+ const loadedEntriesReport = computedAsync(async () => {
34
+ const periodProperties = e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod);
35
+
36
+ if (periodProperties === undefined) {
37
+ throw new Error(`Unknown period ${globalSettings.lastEntriesPeriod}`);
38
+ }
39
+
40
+ const period = periodProperties.seconds;
41
+
42
+ // force refresh
43
+ globalSettings.dataVersion;
44
+
45
+ const loadedEntries = await api.getLastEntries({
46
+ period: period
47
+ });
48
+
49
+ const report = [];
50
+
51
+ for (const entry of loadedEntries) {
52
+ registerEntry(entry);
53
+ report.push(entry.id);
54
+ }
55
+
56
+ firstTimeEntriesLoading.value = false;
57
+
58
+ return report;
59
+ }, []);
60
+
61
+ const entriesReport = computedAsync(async () => {
62
+ let report = loadedEntriesReport.value.slice();
63
+
64
+ if (!globalSettings.showRead) {
65
+ report = report.filter((entryId) => {
66
+ return !entries.value[entryId].hasMarker(e.Marker.Read);
67
+ });
68
+ }
69
+
70
+ report = report.filter((entryId) => {
71
+ for (const tag of entries.value[entryId].tags) {
72
+ if (excludedTags.value[tag]) {
73
+ return false;
74
+ }
75
+ }
76
+ return true;
77
+ });
78
+
79
+ report = report.filter((entryId) => {
80
+ for (const tag of Object.keys(requiredTags.value)) {
81
+ if (requiredTags.value[tag] && !entries.value[entryId].tags.includes(tag)) {
82
+ return false;
83
+ }
84
+ }
85
+ return true;
86
+ });
87
+
88
+ report = report.sort((a: t.EntryId, b: t.EntryId) => {
89
+ const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
90
+
91
+ if (orderProperties === undefined) {
92
+ throw new Error(`Unknown order ${globalSettings.entriesOrder}`);
93
+ }
94
+
95
+ const field = orderProperties.orderField;
96
+
97
+ const valueA = _.get(entries.value[a], field, null);
98
+ const valueB = _.get(entries.value[b], field, null);
99
+
100
+ if (valueA === null && valueB === null) {
101
+ return 0;
102
+ }
103
+
104
+ if (valueA === null) {
105
+ return 1;
106
+ }
107
+
108
+ if (valueB === null) {
109
+ return -1;
110
+ }
111
+
112
+ if (valueA < valueB) {
113
+ return 1;
114
+ }
115
+
116
+ if (valueA > valueB) {
117
+ return -1;
118
+ }
119
+
120
+ return 0;
121
+ });
122
+
123
+ return report;
124
+ }, []);
125
+
126
+ const reportTagsCount = computed(() => {
127
+ const tagsCount: {[key: string]: number} = {};
128
+
129
+ for (const entryId of entriesReport.value) {
130
+ const entry = entries.value[entryId];
131
+
132
+ for (const tag of entry.tags) {
133
+ if (tag in tagsCount) {
134
+ tagsCount[tag] += 1;
135
+ } else {
136
+ tagsCount[tag] = 1;
137
+ }
138
+ }
139
+ }
140
+
141
+ return tagsCount;
142
+ });
143
+
144
+ function requestFullEntry({entryId}: {entryId: t.EntryId}) {
145
+ if (entryId in entries.value && entries.value[entryId].body !== null) {
146
+ return;
147
+ }
148
+
149
+ requestedEntries.value[entryId] = true;
150
+ }
151
+
152
+ async function loadFullEntries() {
153
+ const ids: t.EntryId[] = Object.keys(requestedEntries.value).map((key) => t.toEntryId(key));
154
+
155
+ if (ids.length === 0) {
156
+ return;
157
+ }
158
+
159
+ const entries = await api.getEntriesByIds({ids: ids});
160
+
161
+ for (const entry of entries) {
162
+ registerEntry(entry);
163
+ }
164
+
165
+ requestedEntries.value = {};
166
+ }
167
+
168
+ const requestedEntriesTimer = new Timer(loadFullEntries, 1000);
169
+
170
+ requestedEntriesTimer.start();
171
+
172
+ async function setMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
173
+ await api.setMarker({entryId: entryId, marker: marker});
174
+
175
+ if (entryId in entries.value) {
176
+ entries.value[entryId].setMarker(marker);
177
+ }
178
+ }
179
+
180
+ async function removeMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
181
+ await api.removeMarker({entryId: entryId, marker: marker});
182
+
183
+ if (entryId in entries.value) {
184
+ entries.value[entryId].removeMarker(marker);
185
+ }
186
+ }
187
+
188
+ function requireTag({tag}: {tag: string}) {
189
+ requiredTags.value[tag] = true;
190
+ excludedTags.value[tag] = false;
191
+ }
192
+
193
+ function excludeTag({tag}: {tag: string}) {
194
+ excludedTags.value[tag] = true;
195
+ requiredTags.value[tag] = false;
196
+ }
197
+
198
+ function resetTag({tag}: {tag: string}) {
199
+ excludedTags.value[tag] = false;
200
+ requiredTags.value[tag] = false;
201
+ }
202
+
203
+ return {
204
+ entries,
205
+ entriesReport,
206
+ reportTagsCount,
207
+ requestFullEntry,
208
+ setMarker,
209
+ removeMarker,
210
+ requireTag,
211
+ excludeTag,
212
+ resetTag,
213
+ requiredTags,
214
+ excludedTags,
215
+ firstTimeEntriesLoading
216
+ };
217
+ });
@@ -0,0 +1,74 @@
1
+ import {ref, watch} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+ import {computedAsync} from "@vueuse/core";
5
+ import {useGlobalState} from "@/stores/globalState";
6
+
7
+ import * as e from "@/logic/enums";
8
+ import * as api from "@/logic/api";
9
+
10
+ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
11
+ const router = useRouter();
12
+ const globalState = useGlobalState();
13
+
14
+ // General
15
+ const mainPanelMode = ref(e.MainPanelMode.Entries);
16
+ const dataVersion = ref(0);
17
+
18
+ // Entries
19
+ const lastEntriesPeriod = ref(e.LastEntriesPeriod.Day3);
20
+ const entriesOrder = ref(e.EntriesOrder.Score);
21
+ const showEntriesTags = ref(true);
22
+ const showRead = ref(true);
23
+
24
+ // Feeds
25
+ const showFeedsDescriptions = ref(true);
26
+ const feedsOrder = ref(e.FeedsOrder.Title);
27
+ const failedFeedsFirst = ref(false);
28
+
29
+ watch(mainPanelMode, (newValue, oldValue) => {
30
+ router.push({name: mainPanelMode.value, params: {}});
31
+ });
32
+
33
+ function updateDataVersion() {
34
+ dataVersion.value++;
35
+ }
36
+
37
+ // backend side settings
38
+ const userSettings = computedAsync(async () => {
39
+ if (!globalState.isLoggedIn) {
40
+ return null;
41
+ }
42
+
43
+ // force refresh
44
+ dataVersion.value;
45
+
46
+ return await api.getUserSettings();
47
+ }, null);
48
+
49
+ const info = computedAsync(async () => {
50
+ if (!globalState.isLoggedIn) {
51
+ return null;
52
+ }
53
+
54
+ // force refresh
55
+ dataVersion.value;
56
+
57
+ return await api.getInfo();
58
+ }, null);
59
+
60
+ return {
61
+ mainPanelMode,
62
+ lastEntriesPeriod,
63
+ entriesOrder,
64
+ showEntriesTags,
65
+ showRead,
66
+ dataVersion,
67
+ updateDataVersion,
68
+ showFeedsDescriptions,
69
+ userSettings,
70
+ info,
71
+ feedsOrder,
72
+ failedFeedsFirst
73
+ };
74
+ });
@@ -0,0 +1,23 @@
1
+ import {ref, watch, computed} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+ import {useSupertokens} from "@/stores/supertokens";
5
+ import * as settings from "@/logic/settings";
6
+
7
+ import * as e from "@/logic/enums";
8
+
9
+ export const useGlobalState = defineStore("globalState", () => {
10
+ const supertokens = useSupertokens();
11
+
12
+ const isLoggedIn = computed(() => {
13
+ if (settings.authMode === settings.AuthMode.SingleUser) {
14
+ return true;
15
+ }
16
+
17
+ return supertokens.isLoggedIn;
18
+ });
19
+
20
+ return {
21
+ isLoggedIn
22
+ };
23
+ });
@@ -0,0 +1,144 @@
1
+ import {ref, watch} from "vue";
2
+ import {useRouter} from "vue-router";
3
+ import {defineStore} from "pinia";
4
+ import {Timer} from "@/logic/timer";
5
+ import SuperTokens from "supertokens-web-js";
6
+ import Session from "supertokens-web-js/recipe/session";
7
+ import Passwordless from "supertokens-web-js/recipe/passwordless";
8
+ import * as passwordless from "supertokens-web-js/recipe/passwordless";
9
+ import {computedAsync} from "@vueuse/core";
10
+
11
+ export const useSupertokens = defineStore("supertokens", () => {
12
+ const isLoggedIn = ref<boolean | null>(null);
13
+
14
+ async function refresh() {
15
+ isLoggedIn.value = await Session.doesSessionExist();
16
+ }
17
+
18
+ const refresher = new Timer(refresh, 1000);
19
+
20
+ const allowResendAfter = ref(60 * 1000);
21
+
22
+ function init({
23
+ apiDomain,
24
+ apiBasePath,
25
+ appName,
26
+ resendAfter
27
+ }: {
28
+ apiDomain: string;
29
+ apiBasePath: string;
30
+ appName: string;
31
+ resendAfter: number;
32
+ }) {
33
+ allowResendAfter.value = resendAfter;
34
+
35
+ SuperTokens.init({
36
+ // enableDebugLogs: true,
37
+ appInfo: {
38
+ apiDomain: apiDomain,
39
+ apiBasePath: apiBasePath,
40
+ appName: appName
41
+ },
42
+ recipeList: [Session.init(), Passwordless.init()]
43
+ });
44
+
45
+ refresher.start();
46
+ }
47
+
48
+ async function processError(err: any) {
49
+ await refresh();
50
+
51
+ if (err.isSuperTokensGeneralError === true) {
52
+ window.alert(err.message);
53
+ }
54
+ }
55
+
56
+ async function sendMagicLink(email: string) {
57
+ try {
58
+ let response = await passwordless.createCode({
59
+ email
60
+ });
61
+
62
+ return response.status === "OK";
63
+ } catch (err: any) {
64
+ await processError(err);
65
+ return false;
66
+ }
67
+ }
68
+
69
+ async function logout() {
70
+ await Session.signOut();
71
+
72
+ await passwordless.clearLoginAttemptInfo();
73
+
74
+ await refresh();
75
+ }
76
+
77
+ async function resendMagicLink() {
78
+ try {
79
+ let response = await passwordless.resendCode();
80
+
81
+ if (response.status === "OK") {
82
+ return true;
83
+ } else if (response.status === "RESTART_FLOW_ERROR") {
84
+ // this can happen if the user has already successfully logged in into
85
+ // another device whilst also trying to login to this one.
86
+ return false;
87
+ } else {
88
+ return false;
89
+ }
90
+ } catch (err: any) {
91
+ await processError(err);
92
+ return false;
93
+ }
94
+ }
95
+
96
+ async function hasInitialMagicLinkBeenSent() {
97
+ return (await passwordless.getLoginAttemptInfo()) !== undefined;
98
+ }
99
+
100
+ async function handleMagicLinkClicked({
101
+ onSignUp,
102
+ onSignIn,
103
+ onSignFailed
104
+ }: {
105
+ onSignUp: () => void;
106
+ onSignIn: () => void;
107
+ onSignFailed: () => void;
108
+ }) {
109
+ try {
110
+ let response = await passwordless.consumeCode();
111
+
112
+ await refresh();
113
+
114
+ if (response.status === "OK") {
115
+ if (response.createdNewUser) {
116
+ await onSignUp();
117
+ } else {
118
+ await onSignIn();
119
+ }
120
+ } else {
121
+ await onSignFailed();
122
+ }
123
+ } catch (err: any) {
124
+ await processError(err);
125
+ }
126
+ }
127
+
128
+ async function clearLoginAttempt() {
129
+ await passwordless.clearLoginAttemptInfo();
130
+ }
131
+
132
+ return {
133
+ allowResendAfter,
134
+
135
+ init,
136
+ sendMagicLink,
137
+ resendMagicLink,
138
+ isLoggedIn,
139
+ logout,
140
+ hasInitialMagicLinkBeenSent,
141
+ handleMagicLinkClicked,
142
+ clearLoginAttempt
143
+ };
144
+ });
@@ -0,0 +1,54 @@
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 useTagsStore = defineStore("tagsStore", () => {
12
+ const tags = ref<{[key: string]: t.TagInfo}>({});
13
+ const requestedTags = ref<{[key: string]: boolean}>({});
14
+
15
+ const firstTimeTagsLoading = ref(true);
16
+
17
+ function registerTag(tag: t.TagInfo) {
18
+ tags.value[tag.uid] = tag;
19
+ }
20
+
21
+ function requestTagInfo({tagUid}: {tagUid: string}) {
22
+ if (tagUid in tags.value) {
23
+ return;
24
+ }
25
+
26
+ requestedTags.value[tagUid] = true;
27
+ }
28
+
29
+ async function loadTagsInfo() {
30
+ const uids = Object.keys(requestedTags.value);
31
+
32
+ if (uids.length === 0) {
33
+ return;
34
+ }
35
+
36
+ const tagsInfo = await api.getTagsInfo({uids: uids});
37
+
38
+ for (const uid in tagsInfo) {
39
+ const tag = tagsInfo[uid];
40
+ registerTag(tag);
41
+ }
42
+
43
+ requestedTags.value = {};
44
+ }
45
+
46
+ const requestedTagsTimer = new Timer(loadTagsInfo, 1000);
47
+
48
+ requestedTagsTimer.start();
49
+
50
+ return {
51
+ tags,
52
+ requestTagInfo
53
+ };
54
+ });
@@ -0,0 +1,27 @@
1
+ <template>
2
+ {{ text }}
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import {computed} from "vue";
7
+ import * as u from "@/logic/utils";
8
+
9
+ const properties = defineProps<{
10
+ value: Date | null;
11
+ reversed?: boolean;
12
+ }>();
13
+
14
+ const text = computed(() => {
15
+ if (properties.value === null) {
16
+ return "—";
17
+ }
18
+
19
+ if (properties.reversed) {
20
+ return u.timeSince(properties.value);
21
+ }
22
+
23
+ return properties.value.toLocaleString();
24
+ });
25
+ </script>
26
+
27
+ <style></style>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ {{ feedId }}
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import {computed} from "vue";
7
+ import type * as t from "@/logic/types";
8
+
9
+ const properties = defineProps<{value: t.FeedId}>();
10
+
11
+ const feedId = computed(() => {
12
+ if (properties.value == null) {
13
+ return `—`;
14
+ }
15
+
16
+ const id = properties.value;
17
+
18
+ return id.slice(0, 3) + "***" + id.slice(-3);
19
+ });
20
+ </script>
21
+
22
+ <style></style>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div
3
+ class="score"
4
+ @click.prevent="onClick()">
5
+ {{ value }}
6
+ </div>
7
+ </template>
8
+
9
+ <script lang="ts" setup>
10
+ import * as api from "@/logic/api";
11
+ import type * as t from "@/logic/types";
12
+
13
+ const properties = defineProps<{value: number; entryId: t.EntryId}>();
14
+
15
+ async function onClick() {
16
+ const rules = await api.getScoreDetails({entryId: properties.entryId});
17
+
18
+ if (rules.length === 0) {
19
+ alert("No rules for this news");
20
+ return;
21
+ }
22
+
23
+ rules.sort((a, b) => b.score - a.score);
24
+
25
+ const strings = [];
26
+
27
+ for (const rule of rules) {
28
+ strings.push(rule.score.toString().padStart(2, " ") + " — " + rule.tags.join(", "));
29
+ }
30
+
31
+ alert(strings.join("\n"));
32
+ }
33
+ </script>
34
+
35
+ <style scoped>
36
+ .score {
37
+ display: inline-block;
38
+ cursor: pointer;
39
+ padding: 0.25rem;
40
+ background-color: #c1c1ff;
41
+ }
42
+ </style>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <a
3
+ :href="value"
4
+ target="_blank"
5
+ rel="noopener noreferrer"
6
+ >{{ renderedText }}</a
7
+ >
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import {computed} from "vue";
12
+ import type * as t from "@/logic/types";
13
+
14
+ const properties = defineProps<{value: t.URL; text?: string | null}>();
15
+
16
+ const renderedText = computed(() => {
17
+ if (properties.text) {
18
+ return properties.text;
19
+ }
20
+
21
+ return properties.value;
22
+ });
23
+ </script>
24
+
25
+ <style></style>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <wide-layout>
3
+ <template #header> Feeds Fun </template>
4
+
5
+ <div v-if="!linkProcessed">Checking login status...</div>
6
+
7
+ <div v-else-if="globalState.isLoggedIn">
8
+ <button @click="goToWorkspace()">Go To Feeds</button>
9
+ </div>
10
+
11
+ <div v-else>
12
+ <supertokens-login />
13
+ </div>
14
+ </wide-layout>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import {useRouter} from "vue-router";
19
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
20
+ import {useSupertokens} from "@/stores/supertokens";
21
+ import {useGlobalState} from "@/stores/globalState";
22
+ import {computedAsync} from "@vueuse/core";
23
+ import {computed, ref, onBeforeMount} from "vue";
24
+ import * as settings from "@/logic/settings";
25
+
26
+ const globalSettings = useGlobalSettingsStore();
27
+ const globalState = useGlobalState();
28
+
29
+ const supertokens = useSupertokens();
30
+
31
+ const router = useRouter();
32
+
33
+ const linkProcessed = ref(false);
34
+
35
+ function goToWorkspace() {
36
+ router.push({name: globalSettings.mainPanelMode, params: {}});
37
+ }
38
+
39
+ onBeforeMount(async () => {
40
+ if (globalState.isLoggedIn) {
41
+ goToWorkspace();
42
+ return;
43
+ }
44
+
45
+ async function onSignIn() {
46
+ goToWorkspace();
47
+ }
48
+
49
+ async function onSignFailed() {
50
+ await supertokens.clearLoginAttempt();
51
+ }
52
+
53
+ if (await supertokens.hasInitialMagicLinkBeenSent()) {
54
+ await supertokens.handleMagicLinkClicked({
55
+ onSignUp: onSignIn,
56
+ onSignIn: onSignIn,
57
+ onSignFailed: onSignFailed
58
+ });
59
+ linkProcessed.value = true;
60
+ } else {
61
+ linkProcessed.value = true;
62
+ }
63
+ });
64
+ </script>
65
+
66
+ <style></style>