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,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h3>
|
|
4
|
+
<a
|
|
5
|
+
:href="feed.url"
|
|
6
|
+
target="_blank"
|
|
7
|
+
rel="noopener noreferrer">
|
|
8
|
+
{{ feed.title }}
|
|
9
|
+
</a>
|
|
10
|
+
</h3>
|
|
11
|
+
|
|
12
|
+
<p v-html="feed.description" />
|
|
13
|
+
|
|
14
|
+
<ul>
|
|
15
|
+
<li
|
|
16
|
+
v-for="entry in feed.entries"
|
|
17
|
+
:key="entry.url">
|
|
18
|
+
<entry-info :entry="entry" />
|
|
19
|
+
</li>
|
|
20
|
+
</ul>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
import {computed, ref} from "vue";
|
|
26
|
+
import type * as t from "@/logic/types";
|
|
27
|
+
import * as e from "@/logic/enums";
|
|
28
|
+
import * as api from "@/logic/api";
|
|
29
|
+
import {computedAsync} from "@vueuse/core";
|
|
30
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
31
|
+
|
|
32
|
+
const props = defineProps<{feed: t.FeedInfo}>();
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped></style>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<template v-for="item in collections">
|
|
4
|
+
<input
|
|
5
|
+
type="checkbox"
|
|
6
|
+
:id="item"
|
|
7
|
+
:name="item"
|
|
8
|
+
:value="item"
|
|
9
|
+
v-model="selectedCollections"
|
|
10
|
+
checked />
|
|
11
|
+
<label :for="item">{{ item }}</label>
|
|
12
|
+
<br />
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<br />
|
|
16
|
+
|
|
17
|
+
<button @click="subscribe()">Subscribe</button>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import {computed, ref} from "vue";
|
|
23
|
+
import type * as t from "@/logic/types";
|
|
24
|
+
import * as e from "@/logic/enums";
|
|
25
|
+
import * as api from "@/logic/api";
|
|
26
|
+
import {computedAsync} from "@vueuse/core";
|
|
27
|
+
import DOMPurify from "dompurify";
|
|
28
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
29
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
30
|
+
|
|
31
|
+
const selectedCollections = ref<t.FeedsCollectionId[]>([]);
|
|
32
|
+
|
|
33
|
+
const globalSettings = useGlobalSettingsStore();
|
|
34
|
+
|
|
35
|
+
const collections = computedAsync(async () => {
|
|
36
|
+
const collections = await api.getFeedsCollections();
|
|
37
|
+
|
|
38
|
+
for (const collectionId of collections) {
|
|
39
|
+
selectedCollections.value.push(collectionId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return collections;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
async function subscribe() {
|
|
46
|
+
await api.subscribeToFeedsCollections({
|
|
47
|
+
collectionsIds: selectedCollections.value
|
|
48
|
+
});
|
|
49
|
+
globalSettings.updateDataVersion();
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style scoped></style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<template v-if="feeds === null || feeds.length == 0">
|
|
4
|
+
<p>No feeds</p>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<template v-else>
|
|
8
|
+
<ul style="list-style-type: none; margin: 0; padding: 0">
|
|
9
|
+
<li
|
|
10
|
+
v-for="feed in feeds"
|
|
11
|
+
:key="feed.id"
|
|
12
|
+
style="margin-bottom: 0.25rem">
|
|
13
|
+
<feed-for-list :feed="feed" />
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
</template>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
import {computed, ref, onUnmounted, watch} from "vue";
|
|
22
|
+
import type * as t from "@/logic/types";
|
|
23
|
+
|
|
24
|
+
const properties = defineProps<{feeds: Array<t.Feed>}>();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<style scoped></style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<github-button
|
|
4
|
+
:href="repository"
|
|
5
|
+
data-icon="octicon-star"
|
|
6
|
+
>Star</github-button
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
<github-button
|
|
10
|
+
:href="repository + '/discussions'"
|
|
11
|
+
data-show-count="true"
|
|
12
|
+
data-icon="octicon-comment-discussion"
|
|
13
|
+
>Discuss</github-button
|
|
14
|
+
>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script lang="ts" setup>
|
|
19
|
+
const props = defineProps<{repository: string}>();
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style></style>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="classes"
|
|
4
|
+
:title="tooltip"
|
|
5
|
+
@click.prevent="onClick()">
|
|
6
|
+
<slot name="start"> </slot>
|
|
7
|
+
|
|
8
|
+
<span v-if="countMode == 'prefix'">[{{ count }}]</span>
|
|
9
|
+
|
|
10
|
+
{{ tagInfo.name }}
|
|
11
|
+
|
|
12
|
+
<a
|
|
13
|
+
v-if="tagInfo.link"
|
|
14
|
+
:href="tagInfo.link"
|
|
15
|
+
target="_blank"
|
|
16
|
+
@click.stop=""
|
|
17
|
+
style="text-decoration: none"
|
|
18
|
+
rel="noopener noreferrer">
|
|
19
|
+
↗
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
import * as t from "@/logic/types";
|
|
26
|
+
import {computed, ref} from "vue";
|
|
27
|
+
import {useTagsStore} from "@/stores/tags";
|
|
28
|
+
|
|
29
|
+
const tagsStore = useTagsStore();
|
|
30
|
+
|
|
31
|
+
const properties = defineProps<{
|
|
32
|
+
uid: string;
|
|
33
|
+
count?: number | null;
|
|
34
|
+
countMode?: string | null;
|
|
35
|
+
mode?: string | null;
|
|
36
|
+
}>();
|
|
37
|
+
|
|
38
|
+
const tagInfo = computed(() => {
|
|
39
|
+
const tagInfo = tagsStore.tags[properties.uid];
|
|
40
|
+
|
|
41
|
+
if (tagInfo) {
|
|
42
|
+
return tagInfo;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
tagsStore.requestTagInfo({tagUid: properties.uid});
|
|
46
|
+
|
|
47
|
+
return t.noInfoTag(properties.uid);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits(["tag:clicked"]);
|
|
51
|
+
|
|
52
|
+
const classes = computed(() => {
|
|
53
|
+
const result: {[key: string]: boolean} = {
|
|
54
|
+
tag: true
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (properties.mode) {
|
|
58
|
+
result[properties.mode] = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function onClick() {
|
|
65
|
+
emit("tag:clicked", properties.uid);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tooltip = computed(() => {
|
|
69
|
+
if (properties.countMode == "tooltip" && properties.count) {
|
|
70
|
+
return `articles with the tag: ${properties.count} (${properties.uid})`;
|
|
71
|
+
}
|
|
72
|
+
return "";
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<style scoped>
|
|
77
|
+
.tag {
|
|
78
|
+
display: inline-block;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
padding: 0.25rem;
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tag.selected {
|
|
85
|
+
background-color: #ccccff;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.tag.required {
|
|
89
|
+
background-color: #ccffcc;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.tag.excluded {
|
|
93
|
+
background-color: #ffcccc;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<input
|
|
4
|
+
type="file"
|
|
5
|
+
@change="uploadFile" />
|
|
6
|
+
<button
|
|
7
|
+
type="submit"
|
|
8
|
+
@click.prevent="submit"
|
|
9
|
+
>Submit</button
|
|
10
|
+
>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts" setup>
|
|
15
|
+
import {computed, ref} from "vue";
|
|
16
|
+
import * as t from "@/logic/types";
|
|
17
|
+
import * as e from "@/logic/enums";
|
|
18
|
+
import * as api from "@/logic/api";
|
|
19
|
+
import {computedAsync} from "@vueuse/core";
|
|
20
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
21
|
+
|
|
22
|
+
const opmlFile = ref<File | null>(null);
|
|
23
|
+
|
|
24
|
+
function uploadFile(event: Event) {
|
|
25
|
+
opmlFile.value = (event.target as HTMLInputElement).files?.[0] ?? null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function submit() {
|
|
29
|
+
if (opmlFile.value === null) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const reader = new FileReader();
|
|
34
|
+
|
|
35
|
+
reader.readAsText(opmlFile.value);
|
|
36
|
+
const content = await new Promise<string>((resolve) => {
|
|
37
|
+
reader.onload = () => {
|
|
38
|
+
resolve(reader.result as string);
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await api.addOPML({content: content});
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style scoped></style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<tr>
|
|
3
|
+
<td>{{ period }}</td>
|
|
4
|
+
<td style="text-align: right">{{ usage.used }}</td>
|
|
5
|
+
<td style="text-align: right">{{ usage.reserved }}</td>
|
|
6
|
+
<td style="text-align: right">{{ usage.total() }}</td>
|
|
7
|
+
<td style="text-align: right">{{ percents }}%</td>
|
|
8
|
+
</tr>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script lang="ts" setup>
|
|
12
|
+
import {computed, ref, onUnmounted, watch} from "vue";
|
|
13
|
+
import {computedAsync} from "@vueuse/core";
|
|
14
|
+
import * as api from "@/logic/api";
|
|
15
|
+
import type * as t from "@/logic/types";
|
|
16
|
+
import * as e from "@/logic/enums";
|
|
17
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
18
|
+
|
|
19
|
+
const properties = defineProps<{
|
|
20
|
+
usage: t.ResourceHistoryRecord;
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const globalSettings = useGlobalSettingsStore();
|
|
24
|
+
|
|
25
|
+
globalSettings.mainPanelMode = e.MainPanelMode.Settings;
|
|
26
|
+
|
|
27
|
+
const openAIUsage = computedAsync(async () => {
|
|
28
|
+
return await api.getResourceHistory({kind: "openai_tokens"});
|
|
29
|
+
}, null);
|
|
30
|
+
|
|
31
|
+
const period = computed(() => {
|
|
32
|
+
return properties.usage.intervalStartedAt.toLocaleString("default", {month: "long", year: "numeric"});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const percents = computed(() => {
|
|
36
|
+
if (globalSettings.userSettings == null) {
|
|
37
|
+
return "—";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const setting = globalSettings.userSettings["openai_max_tokens_in_month"];
|
|
41
|
+
|
|
42
|
+
if (!setting) {
|
|
43
|
+
return "—";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof setting.value !== "number") {
|
|
47
|
+
return "—";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const limit: number = setting.value;
|
|
51
|
+
const total = properties.usage.total();
|
|
52
|
+
|
|
53
|
+
if (limit == 0) {
|
|
54
|
+
return "—";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return ((total / limit) * 100).toFixed(5);
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style></style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="rule-constructor">
|
|
3
|
+
<p>Select tags to score news by them.</p>
|
|
4
|
+
|
|
5
|
+
<template
|
|
6
|
+
v-for="tag of tags"
|
|
7
|
+
:key="tag">
|
|
8
|
+
<ffun-tag
|
|
9
|
+
:uid="tag"
|
|
10
|
+
mode="required" />
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<br />
|
|
14
|
+
|
|
15
|
+
<score-selector v-model="currentScore" />
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<a
|
|
20
|
+
href="#"
|
|
21
|
+
v-if="canCreateRule"
|
|
22
|
+
@click.prevent="createRule()"
|
|
23
|
+
>create rule</a
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script lang="ts" setup>
|
|
29
|
+
import {computed, ref} from "vue";
|
|
30
|
+
import * as api from "@/logic/api";
|
|
31
|
+
const properties = defineProps<{tags: string[]}>();
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits(["rule-constructor:created"]);
|
|
34
|
+
|
|
35
|
+
// fibonacci numbers
|
|
36
|
+
const scores = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610];
|
|
37
|
+
|
|
38
|
+
const currentScore = ref(1);
|
|
39
|
+
|
|
40
|
+
const canCreateRule = computed(() => {
|
|
41
|
+
return properties.tags.length > 0;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
async function createRule() {
|
|
45
|
+
await api.createRule({tags: properties.tags, score: currentScore.value});
|
|
46
|
+
emit("rule-constructor:created");
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style scoped>
|
|
51
|
+
.rule-constructor {
|
|
52
|
+
padding: 0.25rem;
|
|
53
|
+
margin: 0.25rem;
|
|
54
|
+
border: 1px solid #ccc;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<score-selector
|
|
4
|
+
:modelValue="currentScore"
|
|
5
|
+
@input="updateSelected" />
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script lang="ts" setup>
|
|
10
|
+
import {computed, ref} from "vue";
|
|
11
|
+
import * as api from "@/logic/api";
|
|
12
|
+
import type * as t from "@/logic/types";
|
|
13
|
+
|
|
14
|
+
const properties = defineProps<{
|
|
15
|
+
score: number;
|
|
16
|
+
ruleId: t.RuleId;
|
|
17
|
+
tags: string[];
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
const currentScore = ref(properties.score);
|
|
21
|
+
|
|
22
|
+
async function updateSelected(event: Event) {
|
|
23
|
+
const target = event.target as HTMLInputElement;
|
|
24
|
+
const newScore = Number(target.value);
|
|
25
|
+
await api.updateRule({
|
|
26
|
+
id: properties.ruleId,
|
|
27
|
+
score: newScore,
|
|
28
|
+
tags: properties.tags
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<style scoped></style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<table>
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
<th>score</th>
|
|
6
|
+
<th>tags</th>
|
|
7
|
+
</tr>
|
|
8
|
+
</thead>
|
|
9
|
+
<tbody>
|
|
10
|
+
<tr v-for="rule in rules">
|
|
11
|
+
<td>
|
|
12
|
+
<rule-score-updater
|
|
13
|
+
:score="rule.score"
|
|
14
|
+
:rule-id="rule.id"
|
|
15
|
+
:tags="rule.tags" />
|
|
16
|
+
</td>
|
|
17
|
+
<td>
|
|
18
|
+
<template
|
|
19
|
+
v-for="tag of rule.tags"
|
|
20
|
+
:key="tag">
|
|
21
|
+
<ffun-tag :uid="tag" />
|
|
22
|
+
</template>
|
|
23
|
+
</td>
|
|
24
|
+
<td>
|
|
25
|
+
<a
|
|
26
|
+
href="#"
|
|
27
|
+
@click.prevent="deleteRule(rule.id)"
|
|
28
|
+
>delete</a
|
|
29
|
+
>
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script lang="ts" setup>
|
|
37
|
+
import type * as t from "@/logic/types";
|
|
38
|
+
import * as api from "@/logic/api";
|
|
39
|
+
import * as e from "@/logic/enums";
|
|
40
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
41
|
+
|
|
42
|
+
defineProps<{rules: Array<t.Rule>}>();
|
|
43
|
+
|
|
44
|
+
const globalSettings = useGlobalSettingsStore();
|
|
45
|
+
|
|
46
|
+
async function deleteRule(id: t.RuleId) {
|
|
47
|
+
await api.deleteRule({id: id});
|
|
48
|
+
globalSettings.updateDataVersion();
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style></style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<button
|
|
4
|
+
v-if="canShowMore"
|
|
5
|
+
@click.prevent="showMore()">
|
|
6
|
+
next {{ realShowPerPage }}
|
|
7
|
+
</button>
|
|
8
|
+
|
|
9
|
+
<span v-if="canShowMore && canHide"> | </span>
|
|
10
|
+
|
|
11
|
+
<button
|
|
12
|
+
v-if="canHide"
|
|
13
|
+
@click.prevent="hideAll()"
|
|
14
|
+
>hide</button
|
|
15
|
+
>
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
v-if="counterOnNewLine"
|
|
19
|
+
style="line-height: 0.5rem"
|
|
20
|
+
> </div
|
|
21
|
+
>
|
|
22
|
+
|
|
23
|
+
shown {{ realShowEntries }} / {{ total }}
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts" setup>
|
|
28
|
+
import {computed, ref} from "vue";
|
|
29
|
+
import * as t from "@/logic/types";
|
|
30
|
+
import {computedAsync} from "@vueuse/core";
|
|
31
|
+
|
|
32
|
+
const properties = defineProps<{
|
|
33
|
+
showFromStart: number;
|
|
34
|
+
showPerPage: number;
|
|
35
|
+
total: number;
|
|
36
|
+
counterOnNewLine: boolean;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const showEntries = ref(properties.showFromStart);
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits(["update:showEntries"]);
|
|
42
|
+
|
|
43
|
+
emit("update:showEntries", showEntries.value);
|
|
44
|
+
|
|
45
|
+
function showMore() {
|
|
46
|
+
showEntries.value += properties.showPerPage;
|
|
47
|
+
|
|
48
|
+
if (showEntries.value > properties.total) {
|
|
49
|
+
showEntries.value = properties.total;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
emit("update:showEntries", showEntries.value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hideAll() {
|
|
56
|
+
showEntries.value = properties.showFromStart;
|
|
57
|
+
emit("update:showEntries", showEntries.value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const realShowPerPage = computed(() => {
|
|
61
|
+
return Math.min(properties.showPerPage, properties.total - showEntries.value);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const realShowEntries = computed(() => {
|
|
65
|
+
const size = Math.min(showEntries.value, properties.total);
|
|
66
|
+
|
|
67
|
+
emit("update:showEntries", size);
|
|
68
|
+
|
|
69
|
+
return size;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const canHide = computed(() => {
|
|
73
|
+
return showEntries.value > properties.showFromStart;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const canShowMore = computed(() => {
|
|
77
|
+
return showEntries.value < properties.total;
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<style></style>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<template v-if="!requested">
|
|
4
|
+
<p>Will send you an email with a login link.</p>
|
|
5
|
+
|
|
6
|
+
<p>If you don't have an account, one will be created.</p>
|
|
7
|
+
|
|
8
|
+
<form @submit.prevent="login()">
|
|
9
|
+
<input
|
|
10
|
+
type="email"
|
|
11
|
+
v-model="email"
|
|
12
|
+
required
|
|
13
|
+
placeholder="your-account-email@example.com" />
|
|
14
|
+
<button type="submit">Login</button>
|
|
15
|
+
</form>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<template v-else>
|
|
19
|
+
<p>
|
|
20
|
+
Email with login link has been sent to <strong>{{ email }}</strong>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
class="btn btn-primary"
|
|
26
|
+
:disabled="counting">
|
|
27
|
+
<vue-countdown
|
|
28
|
+
v-if="counting"
|
|
29
|
+
:time="supertokens.allowResendAfter"
|
|
30
|
+
@end="onCountdownEnd"
|
|
31
|
+
v-slot="{totalSeconds}">
|
|
32
|
+
Resend the email in {{ totalSeconds }} seconds.
|
|
33
|
+
</vue-countdown>
|
|
34
|
+
<span
|
|
35
|
+
v-else
|
|
36
|
+
@click.prevent="resend()"
|
|
37
|
+
>Resend email</span
|
|
38
|
+
>
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
<br /><br />
|
|
42
|
+
|
|
43
|
+
<button @click.prevent="onChangeEmail()">Change email</button>
|
|
44
|
+
</template>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script lang="ts" setup>
|
|
49
|
+
import {computed, ref, onMounted} from "vue";
|
|
50
|
+
import * as t from "@/logic/types";
|
|
51
|
+
import * as e from "@/logic/enums";
|
|
52
|
+
import * as api from "@/logic/api";
|
|
53
|
+
import {computedAsync} from "@vueuse/core";
|
|
54
|
+
import DOMPurify from "dompurify";
|
|
55
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
56
|
+
import {useSupertokens} from "@/stores/supertokens";
|
|
57
|
+
import * as settings from "@/logic/settings";
|
|
58
|
+
|
|
59
|
+
const supertokens = useSupertokens();
|
|
60
|
+
|
|
61
|
+
const requested = ref(false);
|
|
62
|
+
|
|
63
|
+
const counting = ref(false);
|
|
64
|
+
|
|
65
|
+
const email = ref("");
|
|
66
|
+
|
|
67
|
+
async function afterEmailSend(success: boolean) {
|
|
68
|
+
if (success) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
window.alert("Something went wrong. Please try again.");
|
|
73
|
+
requested.value = false;
|
|
74
|
+
counting.value = false;
|
|
75
|
+
await supertokens.clearLoginAttempt();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function beforeEmailSend() {
|
|
79
|
+
window.alert("Please check your email for the magic link");
|
|
80
|
+
requested.value = true;
|
|
81
|
+
counting.value = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function login() {
|
|
85
|
+
beforeEmailSend();
|
|
86
|
+
|
|
87
|
+
const success = await supertokens.sendMagicLink(email.value);
|
|
88
|
+
|
|
89
|
+
await afterEmailSend(success);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resend() {
|
|
93
|
+
beforeEmailSend();
|
|
94
|
+
|
|
95
|
+
const success = await supertokens.resendMagicLink();
|
|
96
|
+
|
|
97
|
+
await afterEmailSend(success);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onCountdownEnd() {
|
|
101
|
+
counting.value = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function onChangeEmail() {
|
|
105
|
+
requested.value = false;
|
|
106
|
+
await supertokens.clearLoginAttempt();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onMounted(async () => {
|
|
110
|
+
if (settings.authMode === settings.AuthMode.SingleUser) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
requested.value = await supertokens.hasInitialMagicLinkBeenSent();
|
|
115
|
+
});
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<style scoped></style>
|