feeds-fun 1.2.0 → 1.4.0

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.2.0",
3
+ "version": "1.4.0",
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": [
@@ -1,44 +1,53 @@
1
1
  <template>
2
2
  <div>
3
- <input
4
- type="text"
5
- class="ffun-input mr-1"
6
- v-model="search"
7
- :disabled="loading"
8
- placeholder="Search for feeds" />
9
-
10
- <button
11
- class="ffun-form-button"
12
- :disabled="loading"
13
- @click.prevent="searhedUrl = search">
14
- Search
15
- </button>
16
-
17
- <hr />
18
-
19
- <div v-if="!loading && foundFeeds.length === 0">No feeds found</div>
20
-
21
- <div v-else-if="loading">Searching for feeds…</div>
22
-
23
- <div v-else>
24
- <div
25
- v-for="feed in foundFeeds"
26
- :key="feed.url">
27
- <feed-info :feed="feed" />
28
-
29
- <button
30
- class="ffun-form-button"
31
- v-if="!addedFeeds[feed.url]"
32
- @click.prevent="addFeed(feed.url)">
33
- Add
34
- </button>
35
-
36
- <p
37
- v-else
38
- class="ffun-info-good"
39
- >Feed added</p
40
- >
41
- </div>
3
+ <form @submit.prevent="searhedUrl = search">
4
+ <input
5
+ type="text"
6
+ class="ffun-input mr-1"
7
+ v-model="search"
8
+ :disabled="disableInputs"
9
+ placeholder="Enter a site URL" />
10
+
11
+ <button
12
+ type="submit"
13
+ class="ffun-form-button"
14
+ :disabled="disableInputs">
15
+ Search
16
+ </button>
17
+ </form>
18
+
19
+ <p
20
+ v-if="searching"
21
+ class="ffun-info-attention"
22
+ >Searching for feeds…</p
23
+ >
24
+
25
+ <div v-else-if="foundFeeds === null"></div>
26
+
27
+ <p
28
+ v-else-if="foundFeeds.length === 0"
29
+ class="ffun-info-attention"
30
+ >No feeds found.</p
31
+ >
32
+
33
+ <div
34
+ v-for="feed in foundFeeds"
35
+ :key="feed.url">
36
+ <feed-info :feed="feed" />
37
+
38
+ <button
39
+ class="ffun-form-button"
40
+ v-if="!addedFeeds[feed.url]"
41
+ :disabled="disableInputs"
42
+ @click.prevent="addFeed(feed.url)">
43
+ Add
44
+ </button>
45
+
46
+ <p
47
+ v-else
48
+ class="ffun-info-good"
49
+ >Feed added</p
50
+ >
42
51
  </div>
43
52
  </div>
44
53
  </template>
@@ -52,7 +61,11 @@
52
61
  import {useEntriesStore} from "@/stores/entries";
53
62
 
54
63
  const search = ref("");
55
- const loading = ref(false);
64
+
65
+ const searching = ref(false);
66
+ const adding = ref(false);
67
+
68
+ const disableInputs = computed(() => searching.value || adding.value);
56
69
 
57
70
  const searhedUrl = ref("");
58
71
 
@@ -60,10 +73,10 @@
60
73
 
61
74
  const foundFeeds = computedAsync(async () => {
62
75
  if (searhedUrl.value === "") {
63
- return [];
76
+ return null;
64
77
  }
65
78
 
66
- loading.value = true;
79
+ searching.value = true;
67
80
 
68
81
  let feeds: t.FeedInfo[] = [];
69
82
 
@@ -73,15 +86,19 @@
73
86
  console.error(e);
74
87
  }
75
88
 
76
- loading.value = false;
89
+ searching.value = false;
77
90
 
78
91
  return feeds;
79
- }, []);
92
+ }, null);
80
93
 
81
94
  async function addFeed(url: string) {
82
- addedFeeds.value[url] = true;
95
+ adding.value = true;
83
96
 
84
97
  await api.addFeed({url: url});
98
+
99
+ addedFeeds.value[url] = true;
100
+
101
+ adding.value = false;
85
102
  }
86
103
  </script>
87
104
 
@@ -12,6 +12,15 @@
12
12
  class="w-4 h-4 align-text-bottom mx-1 inline" />
13
13
  </div>
14
14
 
15
+ <div class="flex-shrink-0 text-right">
16
+ <input-marker
17
+ class="w-7 mr-2"
18
+ :marker="e.Marker.Read"
19
+ :entry-id="entryId"
20
+ on-text="read"
21
+ off-text="new" />
22
+ </div>
23
+
15
24
  <div class="flex-grow">
16
25
  <a
17
26
  :href="entry.url"
@@ -30,13 +39,6 @@
30
39
  </div>
31
40
 
32
41
  <div class="flex flex-shrink-0">
33
- <input-marker
34
- class="w-7 mr-2"
35
- :marker="e.Marker.Read"
36
- :entry-id="entryId"
37
- on-text="read"
38
- off-text="new" />
39
-
40
42
  <div class="w-7">
41
43
  <value-date-time
42
44
  :value="timeFor"
@@ -1,27 +1,47 @@
1
1
  <template>
2
2
  <div>
3
- <ul class="mb-1">
4
- <li v-for="item in collections">
5
- <input
6
- class="ffun-checkbox"
7
- type="checkbox"
8
- :id="item"
9
- :name="item"
10
- :value="item"
11
- v-model="selectedCollections"
12
- checked />
13
- <label
14
- class="ml-2"
15
- :for="item"
16
- >{{ item }}</label
17
- >
18
- </li>
19
- </ul>
20
-
21
- <button
22
- @click="subscribe()"
23
- class="ffun-form-button"
24
- >Subscribe</button
3
+ <form @submit.prevent="subscribe">
4
+ <ul class="mb-1">
5
+ <li v-for="item in collections">
6
+ <input
7
+ class="ffun-checkbox"
8
+ type="checkbox"
9
+ :id="item"
10
+ :name="item"
11
+ :value="item"
12
+ v-model="selectedCollections"
13
+ checked />
14
+ <label
15
+ class="ml-2"
16
+ :for="item"
17
+ >{{ item }}</label
18
+ >
19
+ </li>
20
+ </ul>
21
+
22
+ <button
23
+ type="submit"
24
+ class="ffun-form-button"
25
+ >Subscribe</button
26
+ >
27
+ </form>
28
+
29
+ <p
30
+ v-if="loading"
31
+ class="ffun-info-attention"
32
+ >subscribing...</p
33
+ >
34
+
35
+ <p
36
+ v-if="loaded"
37
+ class="ffun-info-good"
38
+ >Feeds added!</p
39
+ >
40
+
41
+ <p
42
+ v-if="error"
43
+ class="ffun-info-bad"
44
+ >Unknown error occurred! Please, try later.</p
25
45
  >
26
46
  </div>
27
47
  </template>
@@ -36,6 +56,10 @@
36
56
  import {useEntriesStore} from "@/stores/entries";
37
57
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
38
58
 
59
+ const loading = ref(false);
60
+ const loaded = ref(false);
61
+ const error = ref(false);
62
+
39
63
  const selectedCollections = ref<t.FeedsCollectionId[]>([]);
40
64
 
41
65
  const globalSettings = useGlobalSettingsStore();
@@ -51,9 +75,26 @@
51
75
  });
52
76
 
53
77
  async function subscribe() {
54
- await api.subscribeToFeedsCollections({
55
- collectionsIds: selectedCollections.value
56
- });
78
+ loading.value = true;
79
+ loaded.value = false;
80
+ error.value = false;
81
+
82
+ try {
83
+ await api.subscribeToFeedsCollections({
84
+ collectionsIds: selectedCollections.value
85
+ });
86
+
87
+ loading.value = false;
88
+ loaded.value = true;
89
+ error.value = false;
90
+ } catch (e) {
91
+ console.error(e);
92
+
93
+ loading.value = false;
94
+ loaded.value = false;
95
+ error.value = true;
96
+ }
97
+
57
98
  globalSettings.updateDataVersion();
58
99
  }
59
100
  </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div class="ffun-info-attention">
3
+ <p> Make your first rule to experience the reader's full capabilities! </p>
4
+
5
+ <ul class="list-decimal list-inside">
6
+ <li>Click a tag under a news.</li>
7
+ <li>Select more tags if needed.</li>
8
+ <li>Set a score for the rule.</li>
9
+ <li>Click "Create Rule".</li>
10
+ <li>See how the news gets organized based on your preferences!</li>
11
+ </ul>
12
+ </div>
13
+ </template>
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <notification-openai-api-key v-if="showOpenAIKeyNotification" />
3
3
  <notification-collections v-if="showCollectionsNotification" />
4
+ <notification-create-rule-help v-if="showCreateRuleHelpNotification" />
4
5
  </template>
5
6
 
6
7
  <script lang="ts" setup>
@@ -10,6 +11,7 @@
10
11
  const properties = defineProps<{
11
12
  openaiApiKey: boolean;
12
13
  collections: boolean;
14
+ createRuleHelp: boolean;
13
15
  }>();
14
16
 
15
17
  const globalSettings = useGlobalSettingsStore();
@@ -26,7 +28,16 @@
26
28
  return properties.collections;
27
29
  });
28
30
 
31
+ const showCreateRuleHelpNotification = computed(() => {
32
+ return !showCollectionsNotification.value && properties.createRuleHelp;
33
+ });
34
+
29
35
  const showOpenAIKeyNotification = computed(() => {
30
- return !showCollectionsNotification.value && properties.openaiApiKey && showApiKeyMessage.value;
36
+ return (
37
+ !showCollectionsNotification.value &&
38
+ !showCreateRuleHelpNotification.value &&
39
+ properties.openaiApiKey &&
40
+ showApiKeyMessage.value
41
+ );
31
42
  });
32
43
  </script>
@@ -1,15 +1,37 @@
1
1
  <template>
2
2
  <div>
3
- <input
4
- type="file"
5
- class="ffun-file-button"
6
- @change="uploadFile" />
7
-
8
- <button
9
- class="ffun-form-button"
10
- type="submit"
11
- @click.prevent="submit"
12
- >Submit</button
3
+ <form @submit.prevent="submit">
4
+ <input
5
+ type="file"
6
+ class="ffun-file-button"
7
+ :disabled="loading"
8
+ @change="uploadFile" />
9
+
10
+ <button
11
+ class="ffun-form-button"
12
+ type="submit"
13
+ :disabled="loading"
14
+ @click.prevent="submit"
15
+ >Submit</button
16
+ >
17
+ </form>
18
+
19
+ <p
20
+ v-if="loading"
21
+ class="ffun-info-attention"
22
+ >Loading...</p
23
+ >
24
+
25
+ <p
26
+ v-if="loaded"
27
+ class="ffun-info-good"
28
+ >Feeds added!</p
29
+ >
30
+
31
+ <p
32
+ v-if="error"
33
+ class="ffun-info-bad"
34
+ >Error occurred! Maybe you chose a wrong file?</p
13
35
  >
14
36
  </div>
15
37
  </template>
@@ -24,8 +46,13 @@
24
46
 
25
47
  const opmlFile = ref<File | null>(null);
26
48
 
49
+ const loading = ref(false);
50
+ const loaded = ref(false);
51
+ const error = ref(false);
52
+
27
53
  function uploadFile(event: Event) {
28
54
  opmlFile.value = (event.target as HTMLInputElement).files?.[0] ?? null;
55
+ loaded.value = false;
29
56
  }
30
57
 
31
58
  async function submit() {
@@ -33,6 +60,10 @@
33
60
  return;
34
61
  }
35
62
 
63
+ loading.value = true;
64
+ loaded.value = false;
65
+ error.value = false;
66
+
36
67
  const reader = new FileReader();
37
68
 
38
69
  reader.readAsText(opmlFile.value);
@@ -42,7 +73,18 @@
42
73
  };
43
74
  });
44
75
 
45
- await api.addOPML({content: content});
76
+ try {
77
+ await api.addOPML({content: content});
78
+
79
+ loading.value = false;
80
+ loaded.value = true;
81
+ error.value = false;
82
+ } catch (e) {
83
+ console.error(e);
84
+ loading.value = false;
85
+ loaded.value = false;
86
+ error.value = true;
87
+ }
46
88
  }
47
89
  </script>
48
90
 
@@ -11,6 +11,8 @@
11
11
  @click.prevent="createOrUpdateRule()"
12
12
  >create rule</a
13
13
  >
14
+
15
+ <p class="ffun-info-attention"> The news list will be updated right after you create a rule. </p>
14
16
  </div>
15
17
  </template>
16
18
 
@@ -1,29 +1,42 @@
1
1
  <template>
2
- <div
3
- v-if="rule !== null"
4
- class="flex mb-1">
5
- <div class="flex-shrink-0 min-w-fit mr-2">
6
- <a
7
- href="#"
8
- class="ffun-normal-link"
9
- @click.prevent="deleteRule()">
10
- remove
11
- </a>
12
- </div>
2
+ <div>
3
+ <div
4
+ v-if="rule !== null"
5
+ class="flex mb-1">
6
+ <div class="flex-shrink-0 min-w-fit mr-2">
7
+ <a
8
+ href="#"
9
+ class="ffun-normal-link"
10
+ @click.prevent="deleteRule()">
11
+ remove
12
+ </a>
13
+ </div>
14
+
15
+ <score-selector
16
+ class="flex-shrink-0 mr-2"
17
+ :modelValue="rule.score"
18
+ @input="updateSelected" />
13
19
 
14
- <rule-score-updater
15
- class="flex-shrink-0 mr-2"
16
- :score="rule.score"
17
- :rule-id="rule.id"
18
- :tags="rule.tags" />
19
-
20
- <div class="flex-grow">
21
- <template
22
- v-for="tag of rule.tags"
23
- :key="tag">
24
- <ffun-tag :uid="tag" />&nbsp;
25
- </template>
20
+ <div class="flex-grow">
21
+ <template
22
+ v-for="tag of rule.tags"
23
+ :key="tag">
24
+ <ffun-tag :uid="tag" />&nbsp;
25
+ </template>
26
+ </div>
26
27
  </div>
28
+
29
+ <p
30
+ v-if="scoreChanged"
31
+ class="ffun-info-good">
32
+ Score updated
33
+ <a
34
+ href="#"
35
+ class="ffun-form-button"
36
+ @click.prevent="scoreChanged = false"
37
+ >close</a
38
+ >
39
+ </p>
27
40
  </div>
28
41
  </template>
29
42
 
@@ -40,8 +53,25 @@
40
53
 
41
54
  const properties = defineProps<{rule: t.Rule}>();
42
55
 
56
+ const scoreChanged = ref(false);
57
+
43
58
  async function deleteRule() {
44
59
  await api.deleteRule({id: properties.rule.id});
45
60
  globalSettings.updateDataVersion();
46
61
  }
62
+
63
+ async function updateSelected(event: Event) {
64
+ const target = event.target as HTMLInputElement;
65
+ const newScore = Number(target.value);
66
+
67
+ await api.updateRule({
68
+ id: properties.rule.id,
69
+ score: newScore,
70
+ tags: properties.rule.tags
71
+ });
72
+
73
+ scoreChanged.value = true;
74
+
75
+ globalSettings.updateDataVersion();
76
+ }
47
77
  </script>
@@ -61,6 +61,6 @@
61
61
  }
62
62
 
63
63
  .unmarked {
64
- @apply text-purple-700 font-bold no-underline;
64
+ @apply text-orange-700 font-bold no-underline;
65
65
  }
66
66
  </style>
@@ -62,7 +62,7 @@
62
62
  :key="mode">
63
63
  <a
64
64
  v-if="globalSettings.mainPanelMode !== mode"
65
- :href="mode"
65
+ :href="router.resolve({name: mode, params: {}}).href"
66
66
  class="ffun-header-link"
67
67
  @click.prevent="router.push({name: mode, params: {}})">
68
68
  {{ props.text }}
@@ -58,15 +58,20 @@ export const LastEntriesPeriodProperties = new Map<LastEntriesPeriod, {text: str
58
58
  export enum EntriesOrder {
59
59
  Score = "score",
60
60
  ScoreToZero = "score-to-zero",
61
+ ScoreBackward = "score-to-",
61
62
  Published = "published",
62
63
  Cataloged = "cataloged"
63
64
  }
64
65
 
65
- export const EntriesOrderProperties = new Map<EntriesOrder, {text: string; orderField: string; timeField: string}>([
66
- [EntriesOrder.Score, {text: "score", orderField: "score", timeField: "catalogedAt"}],
67
- [EntriesOrder.ScoreToZero, {text: "score ~ 0", orderField: "scoreToZero", timeField: "catalogedAt"}],
68
- [EntriesOrder.Published, {text: "published", orderField: "publishedAt", timeField: "publishedAt"}],
69
- [EntriesOrder.Cataloged, {text: "cataloged", orderField: "catalogedAt", timeField: "catalogedAt"}]
66
+ export const EntriesOrderProperties = new Map<
67
+ EntriesOrder,
68
+ {text: string; orderField: string; timeField: string; direction: number}
69
+ >([
70
+ [EntriesOrder.Score, {text: "score", orderField: "score", timeField: "catalogedAt", direction: 1}],
71
+ [EntriesOrder.ScoreToZero, {text: "score ~ 0", orderField: "scoreToZero", timeField: "catalogedAt", direction: 1}],
72
+ [EntriesOrder.ScoreBackward, {text: "score backward", orderField: "score", timeField: "catalogedAt", direction: -1}],
73
+ [EntriesOrder.Published, {text: "published", orderField: "publishedAt", timeField: "publishedAt", direction: 1}],
74
+ [EntriesOrder.Cataloged, {text: "cataloged", orderField: "catalogedAt", timeField: "catalogedAt", direction: 1}]
70
75
  ]);
71
76
 
72
77
  export enum Marker {
package/src/main.ts CHANGED
@@ -14,7 +14,6 @@ import ConfigSelector from "./components/ConfigSelector.vue";
14
14
  import ConfigFlag from "./components/ConfigFlag.vue";
15
15
  import EntryForList from "./components/EntryForList.vue";
16
16
  import RuleConstructor from "./components/RuleConstructor.vue";
17
- import RuleScoreUpdater from "./components/RuleScoreUpdater.vue";
18
17
  import TagsFilter from "./components/TagsFilter.vue";
19
18
  import DiscoveryForm from "./components/DiscoveryForm.vue";
20
19
  import FeedInfo from "./components/FeedInfo.vue";
@@ -29,6 +28,7 @@ import OpenaiTokensUsage from "./components/OpenaiTokensUsage.vue";
29
28
  import FaviconElement from "./components/FaviconElement.vue";
30
29
  import NotificationCollections from "./components/NotificationCollections.vue";
31
30
  import NotificationOpenaiApiKey from "./components/NotificationOpenaiApiKey.vue";
31
+ import NotificationCreateRuleHelp from "./components/NotificationCreateRuleHelp.vue";
32
32
  import Notifications from "./components/Notifications.vue";
33
33
  import RuleForList from "./components/RuleForList.vue";
34
34
  import UserSettingForNotification from "./components/UserSettingForNotification.vue";
@@ -58,7 +58,6 @@ app.component("ConfigSelector", ConfigSelector);
58
58
  app.component("ConfigFlag", ConfigFlag);
59
59
  app.component("EntryForList", EntryForList);
60
60
  app.component("RuleConstructor", RuleConstructor);
61
- app.component("RuleScoreUpdater", RuleScoreUpdater);
62
61
  app.component("TagsFilter", TagsFilter);
63
62
  app.component("DiscoveryForm", DiscoveryForm);
64
63
  app.component("FeedInfo", FeedInfo);
@@ -73,6 +72,7 @@ app.component("OpenaiTokensUsage", OpenaiTokensUsage);
73
72
  app.component("FaviconElement", FaviconElement);
74
73
  app.component("NotificationCollections", NotificationCollections);
75
74
  app.component("NotificationOpenaiApiKey", NotificationOpenaiApiKey);
75
+ app.component("NotificationCreateRuleHelp", NotificationCreateRuleHelp);
76
76
  app.component("Notifications", Notifications);
77
77
  app.component("RuleForList", RuleForList);
78
78
  app.component("UserSettingForNotification", UserSettingForNotification);
@@ -47,6 +47,7 @@
47
47
 
48
48
  <notifications
49
49
  v-if="entriesStore.loadedEntriesReport !== null"
50
+ :create-rule-help="hasEntries && !hasRules"
50
51
  :openai-api-key="true"
51
52
  :collections="!hasEntries" />
52
53
 
@@ -128,11 +129,11 @@
128
129
  }
129
130
 
130
131
  if (valueA < valueB) {
131
- return 1;
132
+ return orderProperties.direction;
132
133
  }
133
134
 
134
135
  if (valueA > valueB) {
135
- return -1;
136
+ return -orderProperties.direction;
136
137
  }
137
138
 
138
139
  return 0;
@@ -167,6 +168,21 @@
167
168
  return entriesNumber.value > 0;
168
169
  });
169
170
 
171
+ const hasRules = computed(() => {
172
+ if (!hasEntries.value) {
173
+ return false;
174
+ }
175
+
176
+ // TODO: is is heuristics (score could be 0 even with rules)
177
+ // must be refactored to something more stable
178
+ for (const entryId of entriesReport.value) {
179
+ if (entriesStore.entries[entryId].score != 0) {
180
+ return true;
181
+ }
182
+ }
183
+ return false;
184
+ });
185
+
170
186
  const timeField = computed(() => {
171
187
  const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
172
188
 
@@ -1,33 +0,0 @@
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>