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,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
+ &nbsp;
8
+ <input
9
+ v-if="editing"
10
+ type="input"
11
+ v-model="value" />
12
+ <span v-else>{{ verboseValue }}</span>
13
+ </label>
14
+
15
+ &nbsp;
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
+ &nbsp;
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>