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,23 @@
1
+ <template>
2
+ <side-panel-layout :reload-button="false">
3
+ <template #main-header> Collections </template>
4
+
5
+ <feeds-collections />
6
+ </side-panel-layout>
7
+ </template>
8
+
9
+ <script lang="ts" setup>
10
+ import {computed, ref, onUnmounted, watch} from "vue";
11
+ import {computedAsync} from "@vueuse/core";
12
+ import * as api from "@/logic/api";
13
+ import * as t from "@/logic/types";
14
+ import * as e from "@/logic/enums";
15
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
16
+ import {useEntriesStore} from "@/stores/entries";
17
+
18
+ const globalSettings = useGlobalSettingsStore();
19
+
20
+ globalSettings.mainPanelMode = e.MainPanelMode.Collections;
21
+ </script>
22
+
23
+ <style></style>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <side-panel-layout :reload-button="false">
3
+ <template #main-header> Discovery </template>
4
+
5
+ <opml-upload />
6
+
7
+ <hr />
8
+
9
+ <discovery-form />
10
+ </side-panel-layout>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import {computed, ref, onUnmounted, watch} from "vue";
15
+ import {computedAsync} from "@vueuse/core";
16
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
17
+ import * as api from "@/logic/api";
18
+ import * as t from "@/logic/types";
19
+ import * as e from "@/logic/enums";
20
+
21
+ const globalSettings = useGlobalSettingsStore();
22
+
23
+ globalSettings.mainPanelMode = e.MainPanelMode.Discovery;
24
+ </script>
25
+
26
+ <style></style>
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <side-panel-layout>
3
+ <template #side-menu-item-1>
4
+ Show descriptions:
5
+ <config-flag
6
+ v-model:flag="globalSettings.showFeedsDescriptions"
7
+ on-text="yes"
8
+ off-text="no" />
9
+ </template>
10
+
11
+ <template #side-menu-item-2>
12
+ Sorted by
13
+ <config-selector
14
+ :values="e.FeedsOrderProperties"
15
+ v-model:property="globalSettings.feedsOrder" />
16
+ </template>
17
+
18
+ <template #side-menu-item-3>
19
+ Show failed
20
+ <config-flag
21
+ v-model:flag="globalSettings.failedFeedsFirst"
22
+ on-text="first"
23
+ off-text="last" />
24
+ </template>
25
+
26
+ <template #main-header>
27
+ Feeds
28
+ <span v-if="sortedFeeds"> [{{ sortedFeeds.length }}] </span>
29
+ </template>
30
+
31
+ <template #main-footer> </template>
32
+
33
+ <feeds-list
34
+ v-if="sortedFeeds"
35
+ :feeds="sortedFeeds" />
36
+ </side-panel-layout>
37
+ </template>
38
+
39
+ <script lang="ts" setup>
40
+ import _ from "lodash";
41
+ import {computed, ref, onUnmounted, watch} from "vue";
42
+ import {computedAsync} from "@vueuse/core";
43
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
44
+ import * as api from "@/logic/api";
45
+ import type * as t from "@/logic/types";
46
+ import * as e from "@/logic/enums";
47
+
48
+ const globalSettings = useGlobalSettingsStore();
49
+
50
+ globalSettings.mainPanelMode = e.MainPanelMode.Feeds;
51
+
52
+ const feeds = computedAsync(async () => {
53
+ // force refresh
54
+ globalSettings.dataVersion;
55
+ return await api.getFeeds();
56
+ }, null);
57
+
58
+ const sortedFeeds = computed(() => {
59
+ if (!feeds.value) {
60
+ return null;
61
+ }
62
+
63
+ let sorted = feeds.value.slice();
64
+
65
+ const orderProperties = e.FeedsOrderProperties.get(globalSettings.feedsOrder);
66
+
67
+ if (!orderProperties) {
68
+ throw new Error(`Invalid order properties: ${globalSettings.feedsOrder}`);
69
+ }
70
+
71
+ const orderField = orderProperties.orderField;
72
+
73
+ const direction = {asc: -1, desc: 1}[orderProperties.orderDirection];
74
+
75
+ if (direction === undefined) {
76
+ throw new Error(`Invalid order direction: ${orderProperties.orderDirection}`);
77
+ }
78
+
79
+ sorted = sorted.sort((a: t.Feed, b: t.Feed) => {
80
+ if (a.isOk && !b.isOk) {
81
+ if (globalSettings.failedFeedsFirst) {
82
+ return 1;
83
+ }
84
+ return -1;
85
+ }
86
+
87
+ if (!a.isOk && b.isOk) {
88
+ if (globalSettings.failedFeedsFirst) {
89
+ return -1;
90
+ }
91
+ return 1;
92
+ }
93
+
94
+ const valueA = _.get(a, orderField, null);
95
+ const valueB = _.get(b, orderField, null);
96
+
97
+ if (valueA === null && valueB === null) {
98
+ return 0;
99
+ }
100
+
101
+ if (valueA === null) {
102
+ return 1 * direction;
103
+ }
104
+
105
+ if (valueB === null) {
106
+ return -1 * direction;
107
+ }
108
+
109
+ if (valueA < valueB) {
110
+ return 1 * direction;
111
+ }
112
+
113
+ if (valueA > valueB) {
114
+ return -1 * direction;
115
+ }
116
+
117
+ return 0;
118
+ });
119
+
120
+ return sorted;
121
+ });
122
+ </script>
123
+
124
+ <style></style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <wide-layout>
3
+ <template #header> Feeds Fun </template>
4
+
5
+ <div v-if="globalState.isLoggedIn">
6
+ <p>You have already logged in.</p>
7
+ <button @click.prevent="goToWorkspace()">Go To Feeds</button>
8
+ </div>
9
+
10
+ <div v-else>
11
+ <supertokens-login />
12
+ </div>
13
+
14
+ <br />
15
+
16
+ <ffun-github-buttons :repository="settings.githubRepo" />
17
+
18
+ <h2>What is it?</h2>
19
+
20
+ <ul style="text-align: left">
21
+ <li>Web-based news reader. Self-hosted, if it is your way.</li>
22
+ <li>Automatically assigns tags to news entries.</li>
23
+ <li>You create rules to score news by tags.</li>
24
+ <li>Then filter and sort news how you want.</li>
25
+ </ul>
26
+
27
+ <h2>You are in control</h2>
28
+
29
+ <ul style="text-align: left">
30
+ <li>No black box recommendation algorithms.</li>
31
+ <li>No "smart" reordering of your news.</li>
32
+ <li>No ads.</li>
33
+ <li>No selling of your data.</li>
34
+ </ul>
35
+
36
+ <h2>What will it become?</h2>
37
+
38
+ <ul style="text-align: left">
39
+ <li>Will become much prettier.</li>
40
+ <li> Will collect not only RSS/ATOM but any news feeds, including private if you allow. </li>
41
+ <li>Will suggest new rules to score & new feeds, if you allow.</li>
42
+ </ul>
43
+ </wide-layout>
44
+ </template>
45
+
46
+ <script lang="ts" setup>
47
+ import {useRouter} from "vue-router";
48
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
49
+ import {useSupertokens} from "@/stores/supertokens";
50
+ import {useGlobalState} from "@/stores/globalState";
51
+ import {computedAsync} from "@vueuse/core";
52
+ import * as settings from "@/logic/settings";
53
+ import * as e from "@/logic/enums";
54
+
55
+ const globalSettings = useGlobalSettingsStore();
56
+ const globalState = useGlobalState();
57
+
58
+ const supertokens = useSupertokens();
59
+
60
+ const router = useRouter();
61
+
62
+ function goToWorkspace() {
63
+ router.push({name: e.MainPanelMode.Entries, params: {}});
64
+ }
65
+ </script>
66
+
67
+ <style></style>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <side-panel-layout>
3
+ <template #side-menu-item-1>
4
+ For the last
5
+ <config-selector
6
+ :values="e.LastEntriesPeriodProperties"
7
+ v-model:property="globalSettings.lastEntriesPeriod" />
8
+ </template>
9
+
10
+ <template #side-menu-item-2>
11
+ Sorted by
12
+ <config-selector
13
+ :values="e.EntriesOrderProperties"
14
+ v-model:property="globalSettings.entriesOrder" />
15
+ </template>
16
+
17
+ <template #side-menu-item-3>
18
+ Show tags:
19
+ <config-flag
20
+ v-model:flag="globalSettings.showEntriesTags"
21
+ on-text="yes"
22
+ off-text="no" />
23
+ </template>
24
+
25
+ <template #side-menu-item-4>
26
+ Show already read:
27
+ <config-flag
28
+ v-model:flag="globalSettings.showRead"
29
+ on-text="yes"
30
+ off-text="no" />
31
+ </template>
32
+
33
+ <template #side-footer>
34
+ <tags-filter
35
+ v-if="globalSettings.mainPanelMode == e.MainPanelMode.Entries"
36
+ :tags="entriesStore.reportTagsCount" />
37
+ </template>
38
+
39
+ <template #main-header>
40
+ News
41
+ <span v-if="entriesNumber > 0">[{{ entriesNumber }}]</span>
42
+ </template>
43
+
44
+ <template #main-footer> </template>
45
+
46
+ <template v-if="!hasEntries && !entriesStore.firstTimeEntriesLoading">
47
+ <p>It looks like you have no news to read.</p>
48
+ <p> Try to subscribe for the feeds collections that we are preparing for you! </p>
49
+ <feeds-collections />
50
+ </template>
51
+
52
+ <entries-list
53
+ :entriesIds="entriesStore.entriesReport"
54
+ :time-field="timeField"
55
+ :show-tags="globalSettings.showEntriesTags"
56
+ :showFromStart="25"
57
+ :showPerPage="25" />
58
+ </side-panel-layout>
59
+ </template>
60
+
61
+ <script lang="ts" setup>
62
+ import {computed, ref, onUnmounted, watch} from "vue";
63
+ import {computedAsync} from "@vueuse/core";
64
+ import * as api from "@/logic/api";
65
+ import * as t from "@/logic/types";
66
+ import * as e from "@/logic/enums";
67
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
68
+ import {useEntriesStore} from "@/stores/entries";
69
+
70
+ const globalSettings = useGlobalSettingsStore();
71
+ const entriesStore = useEntriesStore();
72
+
73
+ globalSettings.mainPanelMode = e.MainPanelMode.Entries;
74
+
75
+ globalSettings.updateDataVersion();
76
+
77
+ const entriesNumber = computed(() => {
78
+ return entriesStore.entriesReport.length;
79
+ });
80
+
81
+ const hasEntries = computed(() => {
82
+ return entriesNumber.value > 0;
83
+ });
84
+
85
+ const timeField = computed(() => {
86
+ const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
87
+
88
+ if (orderProperties === undefined) {
89
+ throw new Error(`Unknown entries order: ${globalSettings.entriesOrder}`);
90
+ }
91
+
92
+ return orderProperties.timeField;
93
+ });
94
+ </script>
95
+
96
+ <style></style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <side-panel-layout :reload-button="false">
3
+ <template #main-header>
4
+ Rules
5
+ <span v-if="rules">[{{ rules.length }}]</span>
6
+ </template>
7
+
8
+ <rules-list
9
+ v-if="rules"
10
+ :rules="rules" />
11
+ </side-panel-layout>
12
+ </template>
13
+
14
+ <script lang="ts" setup>
15
+ import {computed, ref, onUnmounted, watch} from "vue";
16
+ import {computedAsync} from "@vueuse/core";
17
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
18
+ import * as api from "@/logic/api";
19
+ import * as t from "@/logic/types";
20
+ import * as e from "@/logic/enums";
21
+
22
+ const globalSettings = useGlobalSettingsStore();
23
+
24
+ globalSettings.mainPanelMode = e.MainPanelMode.Rules;
25
+
26
+ const rules = computedAsync(async () => {
27
+ // force refresh
28
+ globalSettings.dataVersion;
29
+ return await api.getRules();
30
+ }, null);
31
+ </script>
32
+
33
+ <style></style>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <side-panel-layout :reload-button="false">
3
+ <h2>Info</h2>
4
+
5
+ <ul>
6
+ <li><strong>User id</strong>: {{ userId }}</li>
7
+ </ul>
8
+
9
+ <h2>Settings</h2>
10
+
11
+ <template #main-header> Settings </template>
12
+
13
+ <user-setting
14
+ v-for="(value, kind) of globalSettings.userSettings"
15
+ :kind="kind" />
16
+
17
+ <h2>OpenAI usage</h2>
18
+
19
+ <p>Token usage for your OpenAI key per month.</p>
20
+
21
+ <ul>
22
+ <li> <strong>Used tokens</strong>: the number of tokens in processed requests. </li>
23
+ <li>
24
+ <strong>Reserved tokens</strong>: the number of tokens reserved for requests that currently are processing or
25
+ were not processed correctly.
26
+ </li>
27
+ <li>
28
+ <strong>Total tokens</strong>: the total number of tokens used in the month. Should be not less than the actual
29
+ used tokens, but can be bigger because we reserve more tokens than actually use.
30
+ </li>
31
+ </ul>
32
+
33
+ <p v-if="openAIUsage == null">Loading...</p>
34
+
35
+ <p v-else-if="openAIUsage.length == 0">No usage data</p>
36
+
37
+ <table v-else>
38
+ <thead>
39
+ <tr>
40
+ <th style="padding-left: 1rem">Period</th>
41
+ <th style="padding-left: 1rem">Used tokens</th>
42
+ <th style="padding-left: 1rem">Reserved tokens</th>
43
+ <th style="padding-left: 1rem">Total tokens</th>
44
+ <th style="padding-left: 1rem">% from current maximum</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ <openai-tokens-usage
49
+ :usage="usage"
50
+ v-for="usage of openAIUsage" />
51
+ </tbody>
52
+ </table>
53
+ </side-panel-layout>
54
+ </template>
55
+
56
+ <script lang="ts" setup>
57
+ import {computed, ref, onUnmounted, watch} from "vue";
58
+ import {computedAsync} from "@vueuse/core";
59
+ import * as api from "@/logic/api";
60
+ import * as t from "@/logic/types";
61
+ import * as e from "@/logic/enums";
62
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
63
+
64
+ const globalSettings = useGlobalSettingsStore();
65
+
66
+ globalSettings.mainPanelMode = e.MainPanelMode.Settings;
67
+
68
+ const openAIUsage = computedAsync(async () => {
69
+ return await api.getResourceHistory({kind: "openai_tokens"});
70
+ }, null);
71
+
72
+ const userId = computed(() => {
73
+ if (globalSettings.info == null) {
74
+ return "—";
75
+ }
76
+
77
+ return globalSettings.info.userId;
78
+ });
79
+ </script>
80
+
81
+ <style></style>
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.web.json",
3
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4
+ "exclude": ["src/**/__tests__/*"],
5
+ "compilerOptions": {
6
+ "composite": true,
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "@/*": ["./src/*"]
10
+ }
11
+ }
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.node.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.app.json"
9
+ },
10
+ {
11
+ "path": "./tsconfig.vitest.json"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.node.json",
3
+ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "types": ["node"]
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "exclude": [],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "lib": [],
7
+ "types": ["node", "jsdom"]
8
+ }
9
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { fileURLToPath, URL } from 'node:url'
2
+
3
+ import { defineConfig } from 'vite'
4
+ import vue from '@vitejs/plugin-vue'
5
+
6
+ export default defineConfig({
7
+ plugins: [vue()],
8
+ resolve: {
9
+ alias: {
10
+ '@': fileURLToPath(new URL('./src', import.meta.url))
11
+ }
12
+ },
13
+
14
+ server: {
15
+ proxy: {
16
+ '/api': {
17
+ target: 'http://backend:8000/',
18
+ changeOrigin: true,
19
+ },
20
+ '/supertokens': {
21
+ target: 'http://backend:8000/',
22
+ changeOrigin: true,
23
+ }
24
+ }
25
+ }
26
+ })
@@ -0,0 +1,15 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { mergeConfig } from 'vite'
3
+ import { configDefaults, defineConfig } from 'vitest/config'
4
+ import viteConfig from './vite.config'
5
+
6
+ export default mergeConfig(
7
+ viteConfig,
8
+ defineConfig({
9
+ test: {
10
+ environment: 'jsdom',
11
+ exclude: [...configDefaults.exclude, 'e2e/*'],
12
+ root: fileURLToPath(new URL('./', import.meta.url))
13
+ }
14
+ })
15
+ )