feeds-fun 0.1.1 → 0.2.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/README.md CHANGED
@@ -6,3 +6,5 @@
6
6
  - Then filter and sort news how you want.
7
7
 
8
8
  Service is up and running at https://feeds.fun
9
+
10
+ Documentation can be found in the repository: https://github.com/Tiendil/feeds.fun
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feeds-fun",
3
- "version": "0.1.1",
3
+ "version": "0.2.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": [
@@ -7,7 +7,9 @@
7
7
  </tr>
8
8
  </thead>
9
9
  <tbody>
10
- <tr v-for="rule in rules">
10
+ <tr
11
+ v-for="rule in rules"
12
+ :key="rule.id">
11
13
  <td>
12
14
  <rule-score-updater
13
15
  :score="rule.score"
@@ -13,6 +13,11 @@
13
13
  @tag:deselected="onTagDeselected" />
14
14
  </ul>
15
15
 
16
+ <input
17
+ type="text"
18
+ placeholder="Input part of tag..."
19
+ v-model="tagNameFilter" />
20
+
16
21
  <ul
17
22
  v-if="displayedTags.length > 0"
18
23
  style="list-style: none; padding: 0; margin: 0">
@@ -39,6 +44,9 @@
39
44
 
40
45
  <script lang="ts" setup>
41
46
  import {computed, ref} from "vue";
47
+ import {useTagsStore} from "@/stores/tags";
48
+
49
+ const tagsStore = useTagsStore();
42
50
 
43
51
  const selectedTags = ref<{[key: string]: boolean}>({});
44
52
 
@@ -48,6 +56,8 @@
48
56
 
49
57
  const showEntries = ref(showFromStart.value);
50
58
 
59
+ const tagNameFilter = ref("");
60
+
51
61
  function tagComparator(a: string, b: string) {
52
62
  const aCount = properties.tags[a];
53
63
  const bCount = properties.tags[b];
@@ -101,6 +111,16 @@
101
111
  return selectedTags.value[tag] !== true;
102
112
  });
103
113
 
114
+ values = values.filter((tag) => {
115
+ const tagInfo = tagsStore.tags[tag];
116
+
117
+ if (tagInfo === undefined || tagInfo.name === null) {
118
+ return tag.includes(tagNameFilter.value);
119
+ }
120
+
121
+ return tagInfo.name.includes(tagNameFilter.value);
122
+ });
123
+
104
124
  values.sort(tagComparator);
105
125
 
106
126
  values = values.slice(0, showEntries.value);
@@ -90,3 +90,17 @@ export const FeedsOrderProperties = new Map<FeedsOrder, {text: string; orderFiel
90
90
  [FeedsOrder.Loaded, {text: "loaded", orderField: "loadedAt", orderDirection: "desc"}],
91
91
  [FeedsOrder.Linked, {text: "added", orderField: "linkedAt", orderDirection: "desc"}]
92
92
  ]);
93
+
94
+ export enum RulesOrder {
95
+ Tags = "tags",
96
+ Score = "score",
97
+ Created = "created",
98
+ Updated = "updated"
99
+ }
100
+
101
+ export const RulesOrderProperties = new Map<RulesOrder, {text: string; orderField: string; orderDirection: string}>([
102
+ [RulesOrder.Tags, {text: "tags", orderField: "tags", orderDirection: "asc"}],
103
+ [RulesOrder.Score, {text: "score", orderField: "score", orderDirection: "desc"}],
104
+ [RulesOrder.Created, {text: "created", orderField: "createdAt", orderDirection: "desc"}],
105
+ [RulesOrder.Updated, {text: "updated", orderField: "updatedAt", orderDirection: "desc"}]
106
+ ]);
@@ -221,24 +221,30 @@ export type Rule = {
221
221
  readonly tags: string[];
222
222
  readonly score: number;
223
223
  readonly createdAt: Date;
224
+ readonly updatedAt: Date;
224
225
  };
225
226
 
226
227
  export function ruleFromJSON({
227
228
  id,
228
229
  tags,
229
230
  score,
230
- createdAt
231
+ createdAt,
232
+ updatedAt
231
233
  }: {
232
234
  id: string;
233
235
  tags: string[];
234
236
  score: number;
235
237
  createdAt: string;
238
+ updatedAt: string;
236
239
  }): Rule {
240
+ tags = tags.sort();
241
+
237
242
  return {
238
243
  id: toRuleId(id),
239
244
  tags: tags,
240
245
  score: score,
241
- createdAt: new Date(createdAt)
246
+ createdAt: new Date(createdAt),
247
+ updatedAt: new Date(updatedAt)
242
248
  };
243
249
  }
244
250
 
@@ -311,7 +317,7 @@ export function tagInfoFromJSON({
311
317
  }
312
318
 
313
319
  export function noInfoTag(uid: string): TagInfo {
314
- return {uid, name: null, link: null, categories: []};
320
+ return {uid, name: uid, link: null, categories: []};
315
321
  }
316
322
 
317
323
  export type UserSetting = {
@@ -37,3 +37,23 @@ export function timeSince(date: Date) {
37
37
 
38
38
  return `${yearsPast}y`;
39
39
  }
40
+
41
+ export function compareLexicographically(a: string[], b: string[]) {
42
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
43
+ const comparison = a[i].localeCompare(b[i]);
44
+
45
+ if (comparison !== 0) {
46
+ return comparison;
47
+ }
48
+ }
49
+
50
+ if (a.length > b.length) {
51
+ return 1;
52
+ }
53
+
54
+ if (a.length < b.length) {
55
+ return -1;
56
+ }
57
+
58
+ return 0;
59
+ }
@@ -26,6 +26,9 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
26
26
  const feedsOrder = ref(e.FeedsOrder.Title);
27
27
  const failedFeedsFirst = ref(false);
28
28
 
29
+ // Rules
30
+ const rulesOrder = ref(e.RulesOrder.Tags);
31
+
29
32
  watch(mainPanelMode, (newValue, oldValue) => {
30
33
  router.push({name: mainPanelMode.value, params: {}});
31
34
  });
@@ -69,6 +72,7 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
69
72
  userSettings,
70
73
  info,
71
74
  feedsOrder,
72
- failedFeedsFirst
75
+ failedFeedsFirst,
76
+ rulesOrder
73
77
  };
74
78
  });
@@ -1,13 +1,20 @@
1
1
  <template>
2
- <side-panel-layout :reload-button="false">
2
+ <side-panel-layout :reload-button="true">
3
3
  <template #main-header>
4
4
  Rules
5
5
  <span v-if="rules">[{{ rules.length }}]</span>
6
6
  </template>
7
7
 
8
+ <template #side-menu-item-2>
9
+ Sorted by
10
+ <config-selector
11
+ :values="e.RulesOrderProperties"
12
+ v-model:property="globalSettings.rulesOrder" />
13
+ </template>
14
+
8
15
  <rules-list
9
16
  v-if="rules"
10
- :rules="rules" />
17
+ :rules="sortedRules" />
11
18
  </side-panel-layout>
12
19
  </template>
13
20
 
@@ -15,8 +22,10 @@
15
22
  import {computed, ref, onUnmounted, watch} from "vue";
16
23
  import {computedAsync} from "@vueuse/core";
17
24
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
25
+ import _ from "lodash";
26
+ import * as utils from "@/logic/utils";
18
27
  import * as api from "@/logic/api";
19
- import * as t from "@/logic/types";
28
+ import type * as t from "@/logic/types";
20
29
  import * as e from "@/logic/enums";
21
30
 
22
31
  const globalSettings = useGlobalSettingsStore();
@@ -28,6 +37,60 @@
28
37
  globalSettings.dataVersion;
29
38
  return await api.getRules();
30
39
  }, null);
40
+
41
+ const sortedRules = computed(() => {
42
+ if (!rules.value) {
43
+ return null;
44
+ }
45
+
46
+ const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder);
47
+
48
+ if (!orderProperties) {
49
+ throw new Error(`Invalid order properties: ${globalSettings.rulesOrder}`);
50
+ }
51
+
52
+ const orderField = orderProperties.orderField;
53
+ const direction = {asc: -1, desc: 1}[orderProperties.orderDirection];
54
+
55
+ if (direction === undefined) {
56
+ throw new Error(`Invalid order direction: ${orderProperties.orderDirection}`);
57
+ }
58
+
59
+ let sorted = rules.value.slice();
60
+
61
+ sorted = sorted.sort((a: t.Rule, b: t.Rule) => {
62
+ if (globalSettings.rulesOrder === e.RulesOrder.Tags) {
63
+ return utils.compareLexicographically(a.tags, b.tags);
64
+ }
65
+
66
+ const valueA = _.get(a, orderField, null);
67
+ const valueB = _.get(b, orderField, null);
68
+
69
+ if (valueA === null && valueB === null) {
70
+ return 0;
71
+ }
72
+
73
+ if (valueA === null) {
74
+ return 1 * direction;
75
+ }
76
+
77
+ if (valueB === null) {
78
+ return -1 * direction;
79
+ }
80
+
81
+ if (valueA < valueB) {
82
+ return 1 * direction;
83
+ }
84
+
85
+ if (valueA > valueB) {
86
+ return -1 * direction;
87
+ }
88
+
89
+ return 0;
90
+ });
91
+
92
+ return sorted;
93
+ });
31
94
  </script>
32
95
 
33
96
  <style></style>