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,130 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<ul
|
|
4
|
+
v-if="displayedSelectedTags.length > 0"
|
|
5
|
+
style="list-style: none; padding: 0; margin: 0">
|
|
6
|
+
<tags-filter-element
|
|
7
|
+
v-for="tag of displayedSelectedTags"
|
|
8
|
+
:key="tag"
|
|
9
|
+
:tag="tag"
|
|
10
|
+
:count="tags[tag] ?? 0"
|
|
11
|
+
:selected="true"
|
|
12
|
+
@tag:selected="onTagSelected"
|
|
13
|
+
@tag:deselected="onTagDeselected" />
|
|
14
|
+
</ul>
|
|
15
|
+
|
|
16
|
+
<ul
|
|
17
|
+
v-if="displayedTags.length > 0"
|
|
18
|
+
style="list-style: none; padding: 0; margin: 0">
|
|
19
|
+
<tags-filter-element
|
|
20
|
+
v-for="tag of displayedTags"
|
|
21
|
+
:key="tag"
|
|
22
|
+
:tag="tag"
|
|
23
|
+
:count="tags[tag]"
|
|
24
|
+
:selected="false"
|
|
25
|
+
@tag:selected="onTagSelected"
|
|
26
|
+
@tag:deselected="onTagDeselected" />
|
|
27
|
+
</ul>
|
|
28
|
+
|
|
29
|
+
<hr />
|
|
30
|
+
|
|
31
|
+
<simple-pagination
|
|
32
|
+
:showFromStart="showFromStart"
|
|
33
|
+
:showPerPage="10"
|
|
34
|
+
:total="totalTags"
|
|
35
|
+
:counterOnNewLine="true"
|
|
36
|
+
v-model:showEntries="showEntries" />
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script lang="ts" setup>
|
|
41
|
+
import {computed, ref} from "vue";
|
|
42
|
+
|
|
43
|
+
const selectedTags = ref<{[key: string]: boolean}>({});
|
|
44
|
+
|
|
45
|
+
const properties = defineProps<{tags: {[key: string]: number}}>();
|
|
46
|
+
|
|
47
|
+
const showFromStart = ref(25);
|
|
48
|
+
|
|
49
|
+
const showEntries = ref(showFromStart.value);
|
|
50
|
+
|
|
51
|
+
function tagComparator(a: string, b: string) {
|
|
52
|
+
const aCount = properties.tags[a];
|
|
53
|
+
const bCount = properties.tags[b];
|
|
54
|
+
|
|
55
|
+
if (aCount > bCount) {
|
|
56
|
+
return -1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (aCount < bCount) {
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (a > b) {
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (a < b) {
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const displayedSelectedTags = computed(() => {
|
|
75
|
+
let values = Object.keys(selectedTags.value);
|
|
76
|
+
|
|
77
|
+
values = values.filter((tag) => {
|
|
78
|
+
return selectedTags.value[tag] === true;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
values.sort(tagComparator);
|
|
82
|
+
|
|
83
|
+
return values;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const totalTags = computed(() => {
|
|
87
|
+
// TODO: this is not correct, because selected tags are treated differently
|
|
88
|
+
// depending on their status: required or excluded.
|
|
89
|
+
// => value is not accurate, but it is ok for now
|
|
90
|
+
return Object.keys(properties.tags).length + Object.keys(selectedTags.value).length;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const displayedTags = computed(() => {
|
|
94
|
+
let values = Object.keys(properties.tags);
|
|
95
|
+
|
|
96
|
+
if (values.length === 0) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
values = values.filter((tag) => {
|
|
101
|
+
return selectedTags.value[tag] !== true;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
values.sort(tagComparator);
|
|
105
|
+
|
|
106
|
+
values = values.slice(0, showEntries.value);
|
|
107
|
+
|
|
108
|
+
return values;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function onTagSelected(tag: string) {
|
|
112
|
+
selectedTags.value[tag] = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function onTagDeselected(tag: string) {
|
|
116
|
+
selectedTags.value[tag] = false;
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<style scoped>
|
|
121
|
+
.filter-element {
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
white-space: nowrap;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.filter-element value-tag {
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<li class="filter-element">
|
|
3
|
+
<ffun-tag
|
|
4
|
+
:uid="tag"
|
|
5
|
+
:count="count"
|
|
6
|
+
:count-mode="countMode"
|
|
7
|
+
:mode="mode"
|
|
8
|
+
@tag:clicked="onTagClicked">
|
|
9
|
+
<template #start>
|
|
10
|
+
<a
|
|
11
|
+
v-if="selected"
|
|
12
|
+
href="#"
|
|
13
|
+
@click.prevent="deselect(tag)"
|
|
14
|
+
>[X]</a
|
|
15
|
+
>
|
|
16
|
+
</template>
|
|
17
|
+
</ffun-tag>
|
|
18
|
+
</li>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import {computed, ref} from "vue";
|
|
23
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
24
|
+
|
|
25
|
+
const entriesStore = useEntriesStore();
|
|
26
|
+
|
|
27
|
+
const properties = defineProps<{
|
|
28
|
+
tag: string;
|
|
29
|
+
count: number;
|
|
30
|
+
selected: boolean;
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits(["tag:selected", "tag:deselected"]);
|
|
34
|
+
|
|
35
|
+
const countMode = computed(() => {
|
|
36
|
+
if (properties.selected) {
|
|
37
|
+
return "no";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "prefix";
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const mode = computed(() => {
|
|
44
|
+
if (entriesStore.requiredTags[properties.tag]) {
|
|
45
|
+
return "required";
|
|
46
|
+
} else if (entriesStore.excludedTags[properties.tag]) {
|
|
47
|
+
return "excluded";
|
|
48
|
+
} else if (properties.selected) {
|
|
49
|
+
return "selected";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function onTagClicked(tag: string) {
|
|
56
|
+
if (entriesStore.requiredTags[properties.tag]) {
|
|
57
|
+
switchToExcluded(tag);
|
|
58
|
+
} else {
|
|
59
|
+
switchToRequired(tag);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function switchToExcluded(tag: string) {
|
|
64
|
+
entriesStore.excludeTag({tag: tag});
|
|
65
|
+
emit("tag:selected", properties.tag);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function switchToRequired(tag: string) {
|
|
69
|
+
entriesStore.requireTag({tag: tag});
|
|
70
|
+
emit("tag:selected", properties.tag);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function deselect(tag: string) {
|
|
74
|
+
entriesStore.resetTag({tag: tag});
|
|
75
|
+
emit("tag:deselected", properties.tag);
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
.filter-element {
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.filter-element value-tag {
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
text-overflow: ellipsis;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<rule-constructor
|
|
4
|
+
v-if="selectedTagsList.length > 0"
|
|
5
|
+
:tags="selectedTagsList"
|
|
6
|
+
@rule-constructor:created="onRuleCreated" />
|
|
7
|
+
|
|
8
|
+
<ffun-tag
|
|
9
|
+
v-for="tag of displayedTags"
|
|
10
|
+
:key="tag"
|
|
11
|
+
:uid="tag"
|
|
12
|
+
:mode="!!selectedTags[tag] ? 'selected' : null"
|
|
13
|
+
:count="entriesStore.reportTagsCount[tag]"
|
|
14
|
+
count-mode="tooltip"
|
|
15
|
+
@tag:clicked="onTagClicked" />
|
|
16
|
+
|
|
17
|
+
<a
|
|
18
|
+
href="#"
|
|
19
|
+
v-if="canShowAll"
|
|
20
|
+
@click.prevent="showAll = true"
|
|
21
|
+
>{{ tagsNumber - showLimit }} more</a
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
<a
|
|
25
|
+
href="#"
|
|
26
|
+
v-if="canHide"
|
|
27
|
+
@click.prevent="showAll = false"
|
|
28
|
+
>hide</a
|
|
29
|
+
>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script lang="ts" setup>
|
|
34
|
+
import {computed, ref} from "vue";
|
|
35
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
36
|
+
|
|
37
|
+
const entriesStore = useEntriesStore();
|
|
38
|
+
|
|
39
|
+
const showAll = ref(false);
|
|
40
|
+
const showLimit = ref(5);
|
|
41
|
+
|
|
42
|
+
const selectedTags = ref<{[key: string]: boolean}>({});
|
|
43
|
+
|
|
44
|
+
const properties = defineProps<{tags: string[]}>();
|
|
45
|
+
|
|
46
|
+
const tagsNumber = computed(() => {
|
|
47
|
+
return properties.tags.length;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const displayedTags = computed(() => {
|
|
51
|
+
if (showAll.value) {
|
|
52
|
+
return preparedTags.value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return preparedTags.value.slice(0, showLimit.value);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const preparedTags = computed(() => {
|
|
59
|
+
const values = [];
|
|
60
|
+
|
|
61
|
+
for (const tag of properties.tags) {
|
|
62
|
+
values.push(tag);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
values.sort((a, b) => {
|
|
66
|
+
const aCount = entriesStore.reportTagsCount[a];
|
|
67
|
+
const bCount = entriesStore.reportTagsCount[b];
|
|
68
|
+
|
|
69
|
+
if (aCount > bCount) {
|
|
70
|
+
return -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (aCount < bCount) {
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (a > b) {
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (a < b) {
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return 0;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return values;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const canShowAll = computed(() => {
|
|
92
|
+
return !showAll.value && showLimit.value < preparedTags.value.length;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const canHide = computed(() => {
|
|
96
|
+
return showAll.value && showLimit.value < preparedTags.value.length;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function onTagClicked(tag: string) {
|
|
100
|
+
if (!!selectedTags.value[tag]) {
|
|
101
|
+
delete selectedTags.value[tag];
|
|
102
|
+
} else {
|
|
103
|
+
selectedTags.value[tag] = true;
|
|
104
|
+
showAll.value = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const selectedTagsList = computed(() => {
|
|
109
|
+
const values = [];
|
|
110
|
+
|
|
111
|
+
for (const tag in selectedTags.value) {
|
|
112
|
+
values.push(tag);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
values.sort();
|
|
116
|
+
|
|
117
|
+
return values;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
function onRuleCreated() {
|
|
121
|
+
selectedTags.value = {};
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<style></style>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="setting !== null"
|
|
4
|
+
style="margin-bottom: 1rem">
|
|
5
|
+
<label>
|
|
6
|
+
<strong>{{ setting.name }}:</strong>
|
|
7
|
+
|
|
8
|
+
<input
|
|
9
|
+
v-if="editing"
|
|
10
|
+
type="input"
|
|
11
|
+
v-model="value" />
|
|
12
|
+
<span v-else>{{ verboseValue }}</span>
|
|
13
|
+
</label>
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
<template v-if="setting.type == 'boolean'">
|
|
18
|
+
<button
|
|
19
|
+
v-if="!setting.value"
|
|
20
|
+
@click.prevent="turnOn()"
|
|
21
|
+
>Turn on</button
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
<button
|
|
25
|
+
v-if="setting.value"
|
|
26
|
+
@click.prevent="turnOff()"
|
|
27
|
+
>Turn off</button
|
|
28
|
+
>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<template v-else-if="!editing">
|
|
32
|
+
<button @click.prevent="startEditing()">Edit</button>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<template v-else>
|
|
36
|
+
<button @click.prevent="save()">Save</button>
|
|
37
|
+
|
|
38
|
+
<button @click.prevent="cancel()">Cancel</button>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
v-if="setting.description"
|
|
43
|
+
v-html="setting.description" />
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script lang="ts" setup>
|
|
48
|
+
import {computed, ref, onUnmounted, watch} from "vue";
|
|
49
|
+
import {computedAsync} from "@vueuse/core";
|
|
50
|
+
import * as api from "@/logic/api";
|
|
51
|
+
import * as t from "@/logic/types";
|
|
52
|
+
import * as e from "@/logic/enums";
|
|
53
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
54
|
+
|
|
55
|
+
const globalSettings = useGlobalSettingsStore();
|
|
56
|
+
|
|
57
|
+
const properties = defineProps<{kind: string}>();
|
|
58
|
+
|
|
59
|
+
const value = ref<string | boolean | number | null>(null);
|
|
60
|
+
|
|
61
|
+
const editing = ref(false);
|
|
62
|
+
|
|
63
|
+
const setting = computed(() => {
|
|
64
|
+
if (properties.kind === null) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (globalSettings.userSettings === null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return globalSettings.userSettings[properties.kind];
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const verboseValue = computed(() => {
|
|
76
|
+
if (setting.value === null) {
|
|
77
|
+
return "—";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const v = setting.value.value;
|
|
81
|
+
const type = setting.value.type;
|
|
82
|
+
|
|
83
|
+
if (type == "boolean") {
|
|
84
|
+
return v ? "Yes" : "No";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (v == null || v == "") {
|
|
88
|
+
return "—";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (type == "secret") {
|
|
92
|
+
return "********";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return v;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
async function save() {
|
|
99
|
+
if (value.value === null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await api.setUserSetting({kind: properties.kind, value: value.value});
|
|
104
|
+
globalSettings.updateDataVersion();
|
|
105
|
+
editing.value = false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function cancel() {
|
|
109
|
+
value.value = setting.value && setting.value.value;
|
|
110
|
+
editing.value = false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function startEditing() {
|
|
114
|
+
value.value = setting.value && setting.value.value;
|
|
115
|
+
editing.value = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function turnOn() {
|
|
119
|
+
value.value = true;
|
|
120
|
+
await save();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function turnOff() {
|
|
124
|
+
value.value = false;
|
|
125
|
+
await save();
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
128
|
+
|
|
129
|
+
<style></style>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div style="display: inline-block">
|
|
3
|
+
<template v-if="hasMarker">
|
|
4
|
+
<a
|
|
5
|
+
href="#"
|
|
6
|
+
class="marked"
|
|
7
|
+
@click.prevent="unmark()"
|
|
8
|
+
>{{ onText }}</a
|
|
9
|
+
>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template v-else>
|
|
13
|
+
<a
|
|
14
|
+
href="#"
|
|
15
|
+
class="unmarked"
|
|
16
|
+
@click.prevent="mark()"
|
|
17
|
+
>{{ offText }}</a
|
|
18
|
+
>
|
|
19
|
+
</template>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script lang="ts" setup>
|
|
24
|
+
import {computed, ref} from "vue";
|
|
25
|
+
import * as api from "@/logic/api";
|
|
26
|
+
import type * as e from "@/logic/enums";
|
|
27
|
+
import type * as t from "@/logic/types";
|
|
28
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
29
|
+
|
|
30
|
+
const entriesStore = useEntriesStore();
|
|
31
|
+
|
|
32
|
+
const properties = defineProps<{
|
|
33
|
+
marker: e.Marker;
|
|
34
|
+
entryId: t.EntryId;
|
|
35
|
+
onText: string;
|
|
36
|
+
offText: string;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const hasMarker = computed(() => {
|
|
40
|
+
return entriesStore.entries[properties.entryId].hasMarker(properties.marker);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
async function mark() {
|
|
44
|
+
await entriesStore.setMarker({
|
|
45
|
+
entryId: properties.entryId,
|
|
46
|
+
marker: properties.marker
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function unmark() {
|
|
51
|
+
await entriesStore.removeMarker({
|
|
52
|
+
entryId: properties.entryId,
|
|
53
|
+
marker: properties.marker
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<style scoped>
|
|
59
|
+
.marked {
|
|
60
|
+
color: #2e8f2e;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.unmarked {
|
|
66
|
+
color: purple;
|
|
67
|
+
/* font-weight: bold; */
|
|
68
|
+
text-decoration: none;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<select
|
|
4
|
+
:value="modelValue"
|
|
5
|
+
@input="updateSelected">
|
|
6
|
+
<option
|
|
7
|
+
v-for="score of scores"
|
|
8
|
+
:value="score"
|
|
9
|
+
:selected="modelValue === score">
|
|
10
|
+
{{ score }}
|
|
11
|
+
</option>
|
|
12
|
+
</select>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts" setup>
|
|
17
|
+
import {computed, ref} from "vue";
|
|
18
|
+
import * as api from "@/logic/api";
|
|
19
|
+
|
|
20
|
+
const properties = withDefaults(defineProps<{scores?: number[]; modelValue: number}>(), {
|
|
21
|
+
scores: () => [
|
|
22
|
+
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, -1, -2, -3, -5, -8, -13, -21, -34, -55, -89, -144, -233,
|
|
23
|
+
-377, -610
|
|
24
|
+
],
|
|
25
|
+
modelValue: 1
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
29
|
+
|
|
30
|
+
function updateSelected(event: Event) {
|
|
31
|
+
const target = event.target as HTMLSelectElement;
|
|
32
|
+
|
|
33
|
+
const newScore = Number(target.value);
|
|
34
|
+
emit("update:modelValue", newScore);
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<style scoped></style>
|