feeds-fun 1.20.5 → 1.20.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feeds-fun",
3
- "version": "1.20.5",
3
+ "version": "1.20.7",
4
4
  "author": "Aliaksei Yaletski (Tiendil) <a.eletsky@gmail.com> (https://tiendil.org/)",
5
5
  "description": "Frontend for the Feeds Fun — web-based news reader",
6
6
  "keywords": [
@@ -2,13 +2,18 @@
2
2
  <button
3
3
  class="ffun-config-flag"
4
4
  @click="emit('update:flag', !flag)">
5
- <span v-if="flag">{{ offText }}</span>
5
+ <span v-if="!flagDefined">&nbsp;</span>
6
+ <span v-else-if="flag">{{ offText }}</span>
6
7
  <span v-else>{{ onText }}</span>
7
8
  </button>
8
9
  </template>
9
10
 
10
11
  <script setup lang="ts">
11
- defineProps<{flag: boolean; onText: string; offText: string}>();
12
+ import {computed} from "vue";
13
+
14
+ const properties = defineProps<{flag: boolean | null; onText: string; offText: string}>();
15
+
16
+ const flagDefined = computed(() => properties.flag !== null && properties.flag !== undefined);
12
17
 
13
18
  const emit = defineEmits(["update:flag"]);
14
19
  </script>
@@ -2,19 +2,28 @@
2
2
  <select
3
3
  class="ffun-input"
4
4
  @change="updateProperty($event)">
5
+ <option
6
+ v-if="!propertyDefined"
7
+ value=""
8
+ selected>
9
+ </option>
10
+
5
11
  <option
6
12
  v-for="[value, props] of values"
7
13
  :value="value"
8
- :selected="value === property">
14
+ :selected="value === property && propertyDefined">
9
15
  {{ props.text }}
10
16
  </option>
11
17
  </select>
12
18
  </template>
13
19
 
14
20
  <script lang="ts" setup>
21
+ import {computed} from "vue";
15
22
  import * as e from "@/logic/enums";
16
23
 
17
- const properties = defineProps<{values: any; property: string}>();
24
+ const properties = defineProps<{values: any; property: string | null}>();
25
+
26
+ const propertyDefined = computed(() => properties.property !== null && properties.property !== undefined);
18
27
 
19
28
  const emit = defineEmits(["update:property"]);
20
29
 
@@ -29,11 +29,11 @@
29
29
  const k = 2;
30
30
 
31
31
  const percents = computed(() => {
32
- if (globalSettings.userSettings == null) {
32
+ if (!globalSettings.userSettingsPresent) {
33
33
  return "—";
34
34
  }
35
35
 
36
- const setting = globalSettings.userSettings["max_tokens_cost_in_month"];
36
+ const setting = globalSettings.max_tokens_cost_in_month;
37
37
 
38
38
  if (!setting) {
39
39
  return "—";
@@ -68,11 +68,11 @@
68
68
  return null;
69
69
  }
70
70
 
71
- if (globalSettings.userSettings === null) {
71
+ if (!globalSettings.userSettingsPresent) {
72
72
  return null;
73
73
  }
74
74
 
75
- return globalSettings.userSettings[properties.kind];
75
+ return (globalSettings as any)[properties.kind];
76
76
  });
77
77
 
78
78
  const verboseValue = computed(() => {
@@ -98,13 +98,13 @@
98
98
  return v;
99
99
  });
100
100
 
101
- async function save() {
101
+ function save() {
102
102
  if (value.value === null) {
103
103
  return;
104
104
  }
105
105
 
106
- await api.setUserSetting({kind: properties.kind, value: value.value});
107
- globalSettings.updateDataVersion();
106
+ globalSettings.setUserSettings(properties.kind, value.value);
107
+
108
108
  editing.value = false;
109
109
  }
110
110
 
@@ -27,11 +27,11 @@
27
27
  return null;
28
28
  }
29
29
 
30
- if (globalSettings.userSettings === null) {
30
+ if (!globalSettings.userSettingsPresent) {
31
31
  return null;
32
32
  }
33
33
 
34
- return globalSettings.userSettings[properties.kind];
34
+ return (globalSettings as any)[properties.kind];
35
35
  });
36
36
 
37
37
  const text = computed(() => {
@@ -46,13 +46,12 @@
46
46
  return setting.value.name;
47
47
  });
48
48
 
49
- async function save() {
49
+ function save() {
50
50
  if (value.value === null) {
51
51
  return;
52
52
  }
53
53
 
54
- await api.setUserSetting({kind: properties.kind, value: value.value});
55
- globalSettings.updateDataVersion();
54
+ globalSettings.setUserSettings(properties.kind, value.value);
56
55
  }
57
56
 
58
57
  async function updateFlag(newValue: any) {
@@ -27,18 +27,18 @@
27
27
 
28
28
  const showApiKeyMessage = computed(() => {
29
29
  return (
30
- globalSettings.userSettings &&
31
- !globalSettings.userSettings.openai_api_key.value &&
32
- !globalSettings.userSettings.gemini_api_key.value &&
33
- !globalSettings.userSettings.hide_message_about_setting_up_key.value
30
+ globalSettings.userSettingsPresent &&
31
+ !globalSettings.openai_api_key?.value &&
32
+ !globalSettings.gemini_api_key?.value &&
33
+ !globalSettings.hide_message_about_setting_up_key?.value
34
34
  );
35
35
  });
36
36
 
37
37
  const showCollectionsNotification = computed(() => {
38
38
  return (
39
39
  properties.collectionsNotification_ &&
40
- globalSettings.userSettings &&
41
- !globalSettings.userSettings.hide_message_about_adding_collections.value &&
40
+ globalSettings.userSettingsPresent &&
41
+ !globalSettings.hide_message_about_adding_collections?.value &&
42
42
  !tagsStates?.value.hasSelectedTags &&
43
43
  settings.hasCollections
44
44
  );
@@ -61,8 +61,8 @@
61
61
  return (
62
62
  properties.collectionsWarning_ &&
63
63
  !showCollectionsNotification.value &&
64
- globalSettings.userSettings &&
65
- !globalSettings.userSettings.hide_message_check_your_feed_urls.value
64
+ globalSettings.userSettingsPresent &&
65
+ !globalSettings.hide_message_check_your_feed_urls?.value
66
66
  );
67
67
  });
68
68
  </script>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <a
3
3
  href="#"
4
- class="ffun-page-header-link py-2 flex items-center justify-center"
4
+ class="ffun-page-header-link py-2 flex items-center justify-center relative"
5
5
  :title="title"
6
6
  @click.prevent="onClick">
7
7
  <icon
8
- v-if="globalSettings.showSidebar"
8
+ v-if="showSidebar"
9
9
  icon="sidebar-left-collapse"
10
10
  size="large" />
11
11
  <icon
@@ -15,7 +15,7 @@
15
15
 
16
16
  <span
17
17
  v-if="showPoint"
18
- class="absolute top-0 right-0 h-2 w-2 rounded-full bg-red-500 border border-white"></span>
18
+ class="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-red-500 border border-white"></span>
19
19
  </a>
20
20
  </template>
21
21
 
@@ -40,8 +40,12 @@
40
40
  return globalSettings.showSidebar ? "Hide sidebar" : "Show sidebar";
41
41
  });
42
42
 
43
+ const showSidebar = computed(() => {
44
+ return globalSettings.showSidebar || !globalSettings.userSettingsPresent;
45
+ });
46
+
43
47
  const showPoint = computed(() => {
44
- return !globalSettings.showSidebar && globalSettings.showSidebarPoint;
48
+ return !showSidebar.value && globalSettings.showSidebarPoint;
45
49
  });
46
50
 
47
51
  function onClick() {
@@ -35,7 +35,7 @@
35
35
  }
36
36
 
37
37
  .ffun-config-flag {
38
- @apply ffun-x-input-common px-2 py-0;
38
+ @apply ffun-x-input-common px-2 py-0 w-12;
39
39
  }
40
40
 
41
41
  .ffun-input {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="ffun-side-panel-layout">
3
3
  <div
4
- v-if="globalSettings.showSidebar"
4
+ v-if="showSidebar"
5
5
  class="ffun-side-panel">
6
6
  <div class="ffun-page-header pr-0 mr-0 flex min-w-full">
7
7
  <div class="ffun-page-header-title grow">
@@ -53,7 +53,7 @@
53
53
  <div class="ffun-body-panel">
54
54
  <div class="ffun-page-header">
55
55
  <div class="ffun-page-header-left-block">
56
- <side-panel-collapse-button v-if="!globalSettings.showSidebar" />
56
+ <side-panel-collapse-button v-if="!showSidebar" />
57
57
 
58
58
  <page-header-home-button v-if="homeButton" />
59
59
 
@@ -162,6 +162,10 @@
162
162
  return !!slots[`side-menu-item-${index}`];
163
163
  }
164
164
 
165
+ const showSidebar = computed(() => {
166
+ return globalSettings.showSidebar || !globalSettings.userSettingsPresent;
167
+ });
168
+
165
169
  watchEffect(() => {
166
170
  if (!properties.loginRequired) {
167
171
  return;
@@ -57,6 +57,7 @@ export enum LastEntriesPeriod {
57
57
  export type LastEntriesPeriodProperty = {
58
58
  readonly text: string;
59
59
  readonly seconds: number;
60
+ readonly default?: boolean;
60
61
  };
61
62
 
62
63
  // Map preserves the order of the keys
@@ -66,7 +67,7 @@ export const LastEntriesPeriodProperties = new Map<LastEntriesPeriod, LastEntrie
66
67
  [LastEntriesPeriod.Hour6, {text: "6 hours", seconds: 6 * c.hour}],
67
68
  [LastEntriesPeriod.Hour12, {text: "12 hours", seconds: 12 * c.hour}],
68
69
  [LastEntriesPeriod.Day1, {text: "1 day", seconds: c.day}],
69
- [LastEntriesPeriod.Day3, {text: "3 days", seconds: 3 * c.day}],
70
+ [LastEntriesPeriod.Day3, {text: "3 days", seconds: 3 * c.day, default: true}],
70
71
  [LastEntriesPeriod.Week, {text: "1 week", seconds: c.week}],
71
72
  [LastEntriesPeriod.Week2, {text: "2 weeks", seconds: 2 * c.week}],
72
73
  [LastEntriesPeriod.Month1, {text: "1 month", seconds: c.month}],
@@ -83,7 +84,7 @@ export const LastEntriesPeriodProperties = new Map<LastEntriesPeriod, LastEntrie
83
84
  export enum EntriesOrder {
84
85
  Score = "score",
85
86
  ScoreToZero = "score-to-zero",
86
- ScoreBackward = "score-to-",
87
+ ScoreBackward = "score-backward",
87
88
  Published = "published",
88
89
  Cataloged = "cataloged"
89
90
  }
@@ -93,19 +94,20 @@ export type EntriesOrderProperty = {
93
94
  readonly orderField: string;
94
95
  readonly timeField: string;
95
96
  readonly direction: number;
97
+ readonly default?: boolean;
96
98
  };
97
99
 
98
100
  export const EntriesOrderProperties = new Map<EntriesOrder, EntriesOrderProperty>([
99
- [EntriesOrder.Score, {text: "score", orderField: "score", timeField: "catalogedAt", direction: 1}],
101
+ [EntriesOrder.Score, {text: "score", orderField: "score", timeField: "catalogedAt", direction: 1, default: true}],
100
102
  [EntriesOrder.ScoreToZero, {text: "score ~ 0", orderField: "scoreToZero", timeField: "catalogedAt", direction: 1}],
101
103
  [EntriesOrder.ScoreBackward, {text: "score backward", orderField: "score", timeField: "catalogedAt", direction: -1}],
102
104
  [EntriesOrder.Published, {text: "published", orderField: "publishedAt", timeField: "publishedAt", direction: 1}],
103
105
  [EntriesOrder.Cataloged, {text: "cataloged", orderField: "catalogedAt", timeField: "catalogedAt", direction: 1}]
104
106
  ]);
105
107
 
106
- ////////////////
108
+ /////////////////////
107
109
  // Min news tag count
108
- ////////////////
110
+ /////////////////////
109
111
 
110
112
  export enum MinNewsTagCount {
111
113
  One = "one",
@@ -118,11 +120,12 @@ export enum MinNewsTagCount {
118
120
  export type MinNewsTagCountProperty = {
119
121
  readonly text: string;
120
122
  readonly count: number;
123
+ readonly default?: boolean;
121
124
  };
122
125
 
123
126
  export const MinNewsTagCountProperties = new Map<MinNewsTagCount, MinNewsTagCountProperty>([
124
127
  [MinNewsTagCount.One, {text: "all", count: 1}],
125
- [MinNewsTagCount.Two, {text: "if in 2+ news", count: 2}],
128
+ [MinNewsTagCount.Two, {text: "if in 2+ news", count: 2, default: true}],
126
129
  [MinNewsTagCount.Three, {text: "if in 3+ news", count: 3}],
127
130
  [MinNewsTagCount.Four, {text: "if in 4+ news", count: 4}],
128
131
  [MinNewsTagCount.Five, {text: "if in 5+ news", count: 5}]
@@ -151,8 +154,15 @@ export enum FeedsOrder {
151
154
  Linked = "linked"
152
155
  }
153
156
 
154
- export const FeedsOrderProperties = new Map<FeedsOrder, {text: string; orderField: string; orderDirection: string}>([
155
- [FeedsOrder.Title, {text: "title", orderField: "title", orderDirection: "asc"}],
157
+ export type FeedsOrderProperty = {
158
+ readonly text: string;
159
+ readonly orderField: string;
160
+ readonly orderDirection: string;
161
+ readonly default?: boolean;
162
+ };
163
+
164
+ export const FeedsOrderProperties = new Map<FeedsOrder, FeedsOrderProperty>([
165
+ [FeedsOrder.Title, {text: "title", orderField: "title", orderDirection: "asc", default: true}],
156
166
  [FeedsOrder.Url, {text: "url", orderField: "url", orderDirection: "asc"}],
157
167
  [FeedsOrder.Loaded, {text: "loaded", orderField: "loadedAt", orderDirection: "desc"}],
158
168
  [FeedsOrder.Linked, {text: "added", orderField: "linkedAt", orderDirection: "desc"}]
@@ -169,8 +179,15 @@ export enum RulesOrder {
169
179
  Updated = "updated"
170
180
  }
171
181
 
172
- export const RulesOrderProperties = new Map<RulesOrder, {text: string; orderField: string; orderDirection: string}>([
173
- [RulesOrder.Tags, {text: "tags", orderField: "tags", orderDirection: "asc"}],
182
+ export type RulesOrderProperty = {
183
+ readonly text: string;
184
+ readonly orderField: string;
185
+ readonly orderDirection: string;
186
+ readonly default?: boolean;
187
+ };
188
+
189
+ export const RulesOrderProperties = new Map<RulesOrder, RulesOrderProperty>([
190
+ [RulesOrder.Tags, {text: "tags", orderField: "tags", orderDirection: "asc", default: true}],
174
191
  [RulesOrder.Score, {text: "score", orderField: "score", orderDirection: "desc"}],
175
192
  [RulesOrder.Created, {text: "created", orderField: "createdAt", orderDirection: "desc"}],
176
193
  [RulesOrder.Updated, {text: "updated", orderField: "updatedAt", orderDirection: "desc"}]
@@ -358,10 +358,12 @@ export function fakeTag({uid, name, link, categories}: TagInfo): TagInfo {
358
358
  return {uid, name, link, categories};
359
359
  }
360
360
 
361
+ export type UserSettingsValue = string | number | boolean;
362
+
361
363
  export type UserSetting = {
362
364
  readonly kind: string;
363
365
  readonly type: string;
364
- value: string | number | boolean;
366
+ value: UserSettingsValue;
365
367
  readonly name: string;
366
368
  };
367
369
 
@@ -369,14 +371,12 @@ export function userSettingFromJSON({
369
371
  kind,
370
372
  type,
371
373
  value,
372
- name,
373
- description
374
+ name
374
375
  }: {
375
376
  kind: string;
376
377
  type: string;
377
378
  value: string | number | boolean;
378
379
  name: string;
379
- description: string;
380
380
  }): UserSetting {
381
381
  return {
382
382
  kind,
@@ -52,14 +52,19 @@ export const useEntriesStore = defineStore("entriesStore", () => {
52
52
  globalSettings.updateDataVersion();
53
53
  }
54
54
 
55
+ const readyToLoadNews = computed(() => {
56
+ return (globalSettings.userSettingsPresent || !globalState.isLoggedIn) && mode.value !== null;
57
+ });
58
+
55
59
  // Public collections uses fixed sorting order
56
60
  // News uses dynamic sorting order and should keep it between switching views
57
61
  // So, if we set globalSettings.entriesOrderProperties in PublicCollection view
58
62
  // we'll break News view sorting and confuse users
59
63
  // => we hardcode specific order properties for PublicCollection mode
60
64
  const activeOrderProperties = computed(() => {
61
- if (mode.value === null) {
62
- // Return most general order for the case when mode is not set yet
65
+ if (!readyToLoadNews.value) {
66
+ // We can not load or process entries until everything is ready
67
+ // => Return most general order
63
68
  return e.EntriesOrderProperties.get(e.EntriesOrder.Published) as unknown as e.EntriesOrderProperty;
64
69
  }
65
70
 
@@ -106,7 +111,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
106
111
  }
107
112
 
108
113
  async function loadEntriesAccordingToMode() {
109
- const periodProperties = e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod);
114
+ const periodProperties = e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod as any);
110
115
 
111
116
  if (periodProperties === undefined) {
112
117
  throw new Error(`Unknown period ${globalSettings.lastEntriesPeriod}`);
@@ -114,7 +119,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
114
119
 
115
120
  const period = periodProperties.seconds;
116
121
 
117
- const minTagCount = e.MinNewsTagCountProperties.get(globalSettings.minTagCount)?.count;
122
+ const minTagCount = e.MinNewsTagCountProperties.get(globalSettings.minTagCount as any)?.count;
118
123
 
119
124
  if (minTagCount === undefined) {
120
125
  throw new Error(`Unknown min tag count ${globalSettings.minTagCount}`);
@@ -142,8 +147,7 @@ export const useEntriesStore = defineStore("entriesStore", () => {
142
147
  // force refresh
143
148
  globalSettings.dataVersion;
144
149
 
145
- if (mode.value === null) {
146
- // Do nothing until the mode is set
150
+ if (!readyToLoadNews.value) {
147
151
  return null;
148
152
  }
149
153
 
@@ -164,6 +168,10 @@ export const useEntriesStore = defineStore("entriesStore", () => {
164
168
  }, null);
165
169
 
166
170
  const _sortedEntries = computed(() => {
171
+ if (!readyToLoadNews.value) {
172
+ return [];
173
+ }
174
+
167
175
  if (loadedEntriesReport.value === null) {
168
176
  return [];
169
177
  }
@@ -1,9 +1,11 @@
1
+ import _ from "lodash";
1
2
  import {ref, computed} from "vue";
2
3
  import {defineStore} from "pinia";
3
4
  import {computedAsync} from "@vueuse/core";
4
5
  import {useGlobalState} from "@/stores/globalState";
5
6
 
6
7
  import * as e from "@/logic/enums";
8
+ import * as t from "@/logic/types";
7
9
  import * as api from "@/logic/api";
8
10
 
9
11
  export const useGlobalSettingsStore = defineStore("globalSettings", () => {
@@ -12,33 +14,13 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
12
14
  // General
13
15
  const mainPanelMode = ref(e.MainPanelMode.Entries);
14
16
  const dataVersion = ref(0);
15
- const showSidebar = ref(true);
16
- const showSidebarPoint = ref(false);
17
-
18
- // Entries
19
- const lastEntriesPeriod = ref(e.LastEntriesPeriod.Day3);
20
- const entriesOrder = ref(e.EntriesOrder.Score);
21
- const minTagCount = ref(e.MinNewsTagCount.Two);
22
- const showRead = ref(true);
23
-
24
- const entriesOrderProperties = computed(() => {
25
- return e.EntriesOrderProperties.get(entriesOrder.value);
26
- });
27
-
28
- // Feeds
29
- const showFeedsDescriptions = ref(true);
30
- const feedsOrder = ref(e.FeedsOrder.Title);
31
- const failedFeedsFirst = ref(false);
32
-
33
- // Rules
34
- const rulesOrder = ref(e.RulesOrder.Tags);
35
17
 
36
18
  function updateDataVersion() {
37
19
  dataVersion.value++;
38
20
  }
39
21
 
40
- // backend side settings
41
- const userSettings = computedAsync(async () => {
22
+ // TODO: We may want to remove this API and move user_id to the user settings API
23
+ const info = computedAsync(async () => {
42
24
  if (!globalState.isLoggedIn) {
43
25
  return null;
44
26
  }
@@ -46,20 +28,195 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
46
28
  // force refresh
47
29
  dataVersion.value;
48
30
 
49
- return await api.getUserSettings();
31
+ return await api.getInfo();
50
32
  }, null);
51
33
 
52
- const info = computedAsync(async () => {
34
+ ///////////////////////////////////////////////////////////
35
+ // Functionality for interaction with backend side settings
36
+ ///////////////////////////////////////////////////////////
37
+
38
+ const _userSettings = computedAsync(async () => {
53
39
  if (!globalState.isLoggedIn) {
54
40
  return null;
55
41
  }
56
42
 
57
- // force refresh
58
43
  dataVersion.value;
59
44
 
60
- return await api.getInfo();
45
+ let settings = await api.getUserSettings();
46
+
47
+ settingsOverrides.value = {};
48
+
49
+ return settings;
61
50
  }, null);
62
51
 
52
+ const userSettingsPresent = computed(() => {
53
+ return _userSettings.value !== null && _userSettings.value !== undefined;
54
+ });
55
+
56
+ function userSettingInfo(kind: string) {
57
+ return computed(() => {
58
+ if (!_userSettings.value || !(kind in _userSettings.value)) {
59
+ return null;
60
+ }
61
+
62
+ const setting = _userSettings.value[kind];
63
+
64
+ let value = setting.value;
65
+
66
+ if (kind in settingsOverrides.value) {
67
+ value = settingsOverrides.value[kind];
68
+ }
69
+
70
+ return {
71
+ kind: setting.kind,
72
+ name: setting.name,
73
+ type: setting.type,
74
+ value: value
75
+ } as t.UserSetting;
76
+ });
77
+ }
78
+
79
+ function _backgroundSetUserSetting(kind: string, value: t.UserSettingsValue) {
80
+ api.setUserSetting({kind: kind, value: value}).catch((error) => {
81
+ console.error(`Error in API call setUserSetting for kind "${kind}":`, error);
82
+ });
83
+ }
84
+
85
+ // This dict is used for two purposes:
86
+ // - To store settings that anonymous user changes while using the site.
87
+ // - To close fast reactive loop after calling backendSettings.set.
88
+ // Without this, setting a setting will cause weired and complex chain
89
+ // of (re)loading data from the backend.
90
+ var settingsOverrides = ref<{[key in keyof any]: t.UserSettingsValue}>({});
91
+
92
+ function setUserSettings(kind: string, newValue: t.UserSettingsValue) {
93
+ settingsOverrides.value[kind] = newValue;
94
+
95
+ if (globalState.isLoggedIn) {
96
+ _backgroundSetUserSetting(kind, newValue);
97
+ }
98
+
99
+ if (_userSettings.value) {
100
+ _userSettings.value[kind].value = newValue;
101
+ }
102
+
103
+ // We do not call updateDataVersion() here
104
+ // Because it causes request of the user settings from the backen
105
+ // which is not required
106
+ // All reactive code should be triggered by changes in settingsOverrides
107
+ }
108
+
109
+ function backendSettings(kind: string, validator: any, defaultValue: t.UserSettingsValue) {
110
+ return computed({
111
+ get() {
112
+ if (kind in settingsOverrides.value) {
113
+ return settingsOverrides.value[kind];
114
+ }
115
+
116
+ if (!globalState.isLoggedIn) {
117
+ return defaultValue;
118
+ }
119
+
120
+ if (!_userSettings.value) {
121
+ return null;
122
+ }
123
+
124
+ const setting = _userSettings.value[kind];
125
+
126
+ if (!validator(setting.value)) {
127
+ _backgroundSetUserSetting(kind, defaultValue);
128
+ return defaultValue;
129
+ }
130
+
131
+ return setting.value;
132
+ },
133
+
134
+ set(newValue) {
135
+ if (newValue === null || newValue === undefined) {
136
+ console.warn(`Setting "${kind}" is set to null or undefined. This is not allowed.`);
137
+ return;
138
+ }
139
+ setUserSettings(kind, newValue);
140
+ }
141
+ });
142
+ }
143
+
144
+ function boolBackendSettings(kind: string, defaultValue: t.UserSettingsValue) {
145
+ return backendSettings(
146
+ kind,
147
+ (rawValue: t.UserSettingsValue) => {
148
+ return typeof rawValue === "boolean";
149
+ },
150
+ defaultValue
151
+ );
152
+ }
153
+
154
+ function enumBackendSettings(kind: string, enumProperties: any) {
155
+ const defaultEntry = _.find([...enumProperties], ([, prop]) => prop.default);
156
+
157
+ if (!defaultEntry) {
158
+ throw new Error(`No default entry found for enum "${kind}"`);
159
+ }
160
+
161
+ let defaultValue = defaultEntry[0];
162
+
163
+ return backendSettings(
164
+ kind,
165
+ (rawValue: t.UserSettingsValue) => {
166
+ return enumProperties.has(rawValue);
167
+ },
168
+ defaultValue
169
+ );
170
+ }
171
+
172
+ ///////////////////////
173
+ // News filter settings
174
+ ///////////////////////
175
+
176
+ const lastEntriesPeriod = enumBackendSettings("view_news_filter_interval", e.LastEntriesPeriodProperties);
177
+ const entriesOrder = enumBackendSettings("view_news_filter_sort_by", e.EntriesOrderProperties);
178
+ const minTagCount = enumBackendSettings("view_news_filter_min_tags_count", e.MinNewsTagCountProperties);
179
+ const showRead = boolBackendSettings("view_news_filter_show_read", true);
180
+
181
+ const entriesOrderProperties = computed(() => {
182
+ return e.EntriesOrderProperties.get(entriesOrder.value as any);
183
+ });
184
+
185
+ ////////////////////////
186
+ // Feeds filter settings
187
+ ////////////////////////
188
+
189
+ const showFeedsDescriptions = boolBackendSettings("view_feeds_show_feed_descriptions", true);
190
+ const feedsOrder = enumBackendSettings("view_feeds_feed_order", e.FeedsOrderProperties);
191
+ const failedFeedsFirst = boolBackendSettings("view_feeds_failed_feeds_first", false);
192
+
193
+ ////////////////////////
194
+ // Rules filter settings
195
+ ////////////////////////
196
+
197
+ const rulesOrder = enumBackendSettings("view_rules_order", e.RulesOrderProperties);
198
+
199
+ ///////////////////
200
+ // Sidebar settings
201
+ ///////////////////
202
+
203
+ const showSidebar = boolBackendSettings("show_sidebar", true);
204
+ const showSidebarPoint = ref(false);
205
+
206
+ ////////////////
207
+ // User settings
208
+ ////////////////
209
+
210
+ const hide_message_about_setting_up_key = userSettingInfo("hide_message_about_setting_up_key");
211
+ const hide_message_about_adding_collections = userSettingInfo("hide_message_about_adding_collections");
212
+ const hide_message_check_your_feed_urls = userSettingInfo("hide_message_check_your_feed_urls");
213
+
214
+ const openai_api_key = userSettingInfo("openai_api_key");
215
+ const gemini_api_key = userSettingInfo("gemini_api_key");
216
+
217
+ const max_tokens_cost_in_month = userSettingInfo("max_tokens_cost_in_month");
218
+ const process_entries_not_older_than = userSettingInfo("process_entries_not_older_than");
219
+
63
220
  return {
64
221
  mainPanelMode,
65
222
  lastEntriesPeriod,
@@ -70,12 +227,23 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
70
227
  dataVersion,
71
228
  updateDataVersion,
72
229
  showFeedsDescriptions,
73
- userSettings,
230
+ userSettingsPresent,
74
231
  info,
75
232
  feedsOrder,
76
233
  failedFeedsFirst,
77
234
  rulesOrder,
78
235
  showSidebar,
79
- showSidebarPoint
236
+ showSidebarPoint,
237
+ setUserSettings,
238
+
239
+ hide_message_about_setting_up_key,
240
+ hide_message_about_adding_collections,
241
+ hide_message_check_your_feed_urls,
242
+
243
+ openai_api_key,
244
+ gemini_api_key,
245
+
246
+ max_tokens_cost_in_month,
247
+ process_entries_not_older_than
80
248
  };
81
249
  });
@@ -80,7 +80,7 @@
80
80
  return [];
81
81
  }
82
82
 
83
- const orderProperties = e.FeedsOrderProperties.get(globalSettings.feedsOrder);
83
+ const orderProperties = e.FeedsOrderProperties.get(globalSettings.feedsOrder as any);
84
84
 
85
85
  if (!orderProperties) {
86
86
  throw new Error(`Invalid order properties: ${globalSettings.feedsOrder}`);
@@ -64,7 +64,7 @@
64
64
 
65
65
  <notifications-loaded-old-news
66
66
  :entries="entriesStore.loadedEntriesReport || []"
67
- :period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod)" />
67
+ :period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod as any)" />
68
68
 
69
69
  <entries-list
70
70
  :loading="entriesStore.loading"
@@ -86,7 +86,7 @@
86
86
 
87
87
  <notifications-loaded-old-news
88
88
  :entries="entriesStore.loadedEntriesReport || []"
89
- :period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod)" />
89
+ :period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod as any)" />
90
90
 
91
91
  <entries-list
92
92
  :loading="entriesStore.loading"
@@ -198,7 +198,7 @@
198
198
 
199
199
  const medianTag1: ComputedRef<string> = computed(() => {
200
200
  // do not change tag when the filter changed
201
- if (tagsStates.value.hasSelectedTags) {
201
+ if (tagsStates.value.hasSelectedTags && medianTag1.value) {
202
202
  return medianTag1.value;
203
203
  }
204
204
 
@@ -215,7 +215,7 @@
215
215
 
216
216
  const medianTag2: ComputedRef<string> = computed(() => {
217
217
  // do not change tag when the filter changed
218
- if (tagsStates.value.hasSelectedTags) {
218
+ if (tagsStates.value.hasSelectedTags && medianTag2.value) {
219
219
  return medianTag2.value;
220
220
  }
221
221
 
@@ -92,7 +92,7 @@
92
92
 
93
93
  sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.tags);
94
94
 
95
- const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder);
95
+ const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder as any);
96
96
 
97
97
  if (!orderProperties) {
98
98
  throw new Error(`Invalid order properties: ${globalSettings.rulesOrder}`);
@@ -130,7 +130,7 @@
130
130
  class="ffun-info-bad">
131
131
  <button
132
132
  @click.prevent="removeAccount()"
133
- class="ffun-form-button bad short ml-1"
133
+ class="ffun-form-button bad short pl-0"
134
134
  >Remove Account</button
135
135
  >
136
136