feeds-fun 1.2.0 → 1.3.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.3.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>
@@ -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";
@@ -58,7 +57,6 @@ app.component("ConfigSelector", ConfigSelector);
58
57
  app.component("ConfigFlag", ConfigFlag);
59
58
  app.component("EntryForList", EntryForList);
60
59
  app.component("RuleConstructor", RuleConstructor);
61
- app.component("RuleScoreUpdater", RuleScoreUpdater);
62
60
  app.component("TagsFilter", TagsFilter);
63
61
  app.component("DiscoveryForm", DiscoveryForm);
64
62
  app.component("FeedInfo", FeedInfo);
@@ -128,11 +128,11 @@
128
128
  }
129
129
 
130
130
  if (valueA < valueB) {
131
- return 1;
131
+ return orderProperties.direction;
132
132
  }
133
133
 
134
134
  if (valueA > valueB) {
135
- return -1;
135
+ return -orderProperties.direction;
136
136
  }
137
137
 
138
138
  return 0;
@@ -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>