feeds-fun 1.15.0 → 1.16.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 +1 -1
- package/src/components/EntriesList.vue +5 -10
- package/src/components/EntryForList.vue +20 -39
- package/src/components/FfunTag.vue +55 -30
- package/src/components/RuleConstructor.vue +56 -27
- package/src/components/RuleForList.vue +14 -3
- package/src/components/TagsFilter.vue +68 -76
- package/src/components/TagsList.vue +14 -66
- package/src/components/notifications/Block.vue +7 -2
- package/src/components/notifications/CreateRuleHelp.vue +16 -9
- package/src/inputs/ScoreSelector.vue +10 -3
- package/src/layouts/SidePanelLayout.vue +1 -1
- package/src/logic/api.ts +26 -4
- package/src/logic/asserts.ts +5 -0
- package/src/logic/tagsFilterState.ts +63 -11
- package/src/logic/types.ts +12 -5
- package/src/stores/entries.ts +27 -1
- package/src/stores/globalSettings.ts +0 -2
- package/src/values/Score.vue +11 -2
- package/src/views/FeedsView.vue +9 -0
- package/src/views/NewsView.vue +51 -51
- package/src/views/RulesView.vue +34 -14
package/src/views/NewsView.vue
CHANGED
|
@@ -15,15 +15,6 @@
|
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<template #side-menu-item-3>
|
|
18
|
-
Show tags:
|
|
19
|
-
<config-flag
|
|
20
|
-
style="min-width: 2.5rem"
|
|
21
|
-
v-model:flag="globalSettings.showEntriesTags"
|
|
22
|
-
on-text="no"
|
|
23
|
-
off-text="yes" />
|
|
24
|
-
</template>
|
|
25
|
-
|
|
26
|
-
<template #side-menu-item-4>
|
|
27
18
|
Show read:
|
|
28
19
|
<config-flag
|
|
29
20
|
style="min-width: 2.5rem"
|
|
@@ -35,7 +26,7 @@
|
|
|
35
26
|
<template #side-footer>
|
|
36
27
|
<tags-filter
|
|
37
28
|
:tags="tagsCount"
|
|
38
|
-
|
|
29
|
+
:show-create-rule="true" />
|
|
39
30
|
</template>
|
|
40
31
|
|
|
41
32
|
<template #main-header>
|
|
@@ -55,16 +46,14 @@
|
|
|
55
46
|
<entries-list
|
|
56
47
|
:entriesIds="entriesReport"
|
|
57
48
|
:time-field="timeField"
|
|
58
|
-
:show-tags="globalSettings.showEntriesTags"
|
|
59
49
|
:tags-count="tagsCount"
|
|
60
50
|
:showFromStart="25"
|
|
61
|
-
:showPerPage="25"
|
|
62
|
-
@entry:bodyVisibilityChanged="onBodyVisibilityChanged" />
|
|
51
|
+
:showPerPage="25" />
|
|
63
52
|
</side-panel-layout>
|
|
64
53
|
</template>
|
|
65
54
|
|
|
66
55
|
<script lang="ts" setup>
|
|
67
|
-
import {computed, ref, onUnmounted, watch} from "vue";
|
|
56
|
+
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
68
57
|
import {computedAsync} from "@vueuse/core";
|
|
69
58
|
import * as api from "@/logic/api";
|
|
70
59
|
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
@@ -79,67 +68,86 @@
|
|
|
79
68
|
|
|
80
69
|
const tagsStates = ref<tagsFilterState.Storage>(new tagsFilterState.Storage());
|
|
81
70
|
|
|
71
|
+
provide("tagsStates", tagsStates);
|
|
72
|
+
|
|
82
73
|
globalSettings.mainPanelMode = e.MainPanelMode.Entries;
|
|
83
74
|
|
|
84
75
|
globalSettings.updateDataVersion();
|
|
85
76
|
|
|
86
77
|
const entriesWithOpenedBody = ref<{[key: t.EntryId]: boolean}>({});
|
|
87
78
|
|
|
88
|
-
const
|
|
79
|
+
const _sortedEntries = computed(() => {
|
|
89
80
|
if (entriesStore.loadedEntriesReport === null) {
|
|
90
81
|
return [];
|
|
91
82
|
}
|
|
92
83
|
|
|
93
|
-
|
|
84
|
+
const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
|
|
94
85
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
if (entriesWithOpenedBody.value[entryId]) {
|
|
98
|
-
// always show read entries with open body
|
|
99
|
-
// otherwise, they will hide right after opening it
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
return !entriesStore.entries[entryId].hasMarker(e.Marker.Read);
|
|
103
|
-
});
|
|
86
|
+
if (orderProperties === undefined) {
|
|
87
|
+
throw new Error(`Unknown order ${globalSettings.entriesOrder}`);
|
|
104
88
|
}
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
report = report.sort((a: t.EntryId, b: t.EntryId) => {
|
|
109
|
-
const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
|
|
90
|
+
const field = orderProperties.orderField;
|
|
91
|
+
const direction = orderProperties.direction;
|
|
110
92
|
|
|
111
|
-
|
|
112
|
-
throw new Error(`Unknown order ${globalSettings.entriesOrder}`);
|
|
113
|
-
}
|
|
93
|
+
// let report = entriesStore.loadedEntriesReport.slice();
|
|
114
94
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
95
|
+
// Pre-map to avoid repeated lookups in the comparator
|
|
96
|
+
const mapped = entriesStore.loadedEntriesReport.map((entryId) => {
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
return {entryId, value: entriesStore.entries[entryId][field]};
|
|
99
|
+
});
|
|
119
100
|
|
|
120
|
-
|
|
101
|
+
mapped.sort((a: {entryId: t.EntryId; value: any}, b: {entryId: t.EntryId; value: any}) => {
|
|
102
|
+
if (a.value === null && b.value === null) {
|
|
121
103
|
return 0;
|
|
122
104
|
}
|
|
123
105
|
|
|
124
|
-
if (
|
|
106
|
+
if (a.value === null) {
|
|
125
107
|
return 1;
|
|
126
108
|
}
|
|
127
109
|
|
|
128
|
-
if (
|
|
110
|
+
if (b.value === null) {
|
|
129
111
|
return -1;
|
|
130
112
|
}
|
|
131
113
|
|
|
132
|
-
if (
|
|
133
|
-
return
|
|
114
|
+
if (a.value < b.value) {
|
|
115
|
+
return direction;
|
|
134
116
|
}
|
|
135
117
|
|
|
136
|
-
if (
|
|
137
|
-
return -
|
|
118
|
+
if (a.value > b.value) {
|
|
119
|
+
return -direction;
|
|
138
120
|
}
|
|
139
121
|
|
|
140
122
|
return 0;
|
|
141
123
|
});
|
|
142
124
|
|
|
125
|
+
const report = mapped.map((x) => x.entryId);
|
|
126
|
+
|
|
127
|
+
return report;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const _visibleEntries = computed(() => {
|
|
131
|
+
let report = _sortedEntries.value.slice();
|
|
132
|
+
|
|
133
|
+
if (!globalSettings.showRead) {
|
|
134
|
+
report = report.filter((entryId) => {
|
|
135
|
+
if (entriesStore.displayedEntryId == entryId) {
|
|
136
|
+
// always show read entries with open body
|
|
137
|
+
// otherwise, they will hide right after opening it
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return !entriesStore.entries[entryId].hasMarker(e.Marker.Read);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return report;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const entriesReport = computed(() => {
|
|
148
|
+
let report = _visibleEntries.value.slice();
|
|
149
|
+
|
|
150
|
+
report = tagsStates.value.filterByTags(report, (entryId) => entriesStore.entries[entryId].tags);
|
|
143
151
|
return report;
|
|
144
152
|
});
|
|
145
153
|
|
|
@@ -193,14 +201,6 @@
|
|
|
193
201
|
|
|
194
202
|
return orderProperties.timeField;
|
|
195
203
|
});
|
|
196
|
-
|
|
197
|
-
function onTagStateChanged({tag, state}: {tag: string; state: tagsFilterState.State}) {
|
|
198
|
-
tagsStates.value.onTagStateChanged({tag, state});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function onBodyVisibilityChanged({entryId, visible}: {entryId: t.EntryId; visible: boolean}) {
|
|
202
|
-
entriesWithOpenedBody.value[entryId] = visible;
|
|
203
|
-
}
|
|
204
204
|
</script>
|
|
205
205
|
|
|
206
206
|
<style></style>
|
package/src/views/RulesView.vue
CHANGED
|
@@ -13,11 +13,21 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<template #side-footer>
|
|
16
|
-
<tags-filter
|
|
17
|
-
:tags="tags"
|
|
18
|
-
@tag:stateChanged="onTagStateChanged" />
|
|
16
|
+
<tags-filter :tags="tagsCount" />
|
|
19
17
|
</template>
|
|
20
18
|
|
|
19
|
+
<div class="ffun-info-good">
|
|
20
|
+
<p
|
|
21
|
+
>You can create new rules on the
|
|
22
|
+
<a
|
|
23
|
+
href="#"
|
|
24
|
+
@click.prevent="goToNews()"
|
|
25
|
+
>news</a
|
|
26
|
+
>
|
|
27
|
+
tab.</p
|
|
28
|
+
>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
21
31
|
<rules-list
|
|
22
32
|
v-if="rules"
|
|
23
33
|
:rules="sortedRules" />
|
|
@@ -25,7 +35,8 @@
|
|
|
25
35
|
</template>
|
|
26
36
|
|
|
27
37
|
<script lang="ts" setup>
|
|
28
|
-
import {computed, ref, onUnmounted, watch} from "vue";
|
|
38
|
+
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
39
|
+
import {useRouter} from "vue-router";
|
|
29
40
|
import {computedAsync} from "@vueuse/core";
|
|
30
41
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
31
42
|
import _ from "lodash";
|
|
@@ -34,19 +45,28 @@
|
|
|
34
45
|
import type * as t from "@/logic/types";
|
|
35
46
|
import * as e from "@/logic/enums";
|
|
36
47
|
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
48
|
+
|
|
49
|
+
const router = useRouter();
|
|
50
|
+
|
|
37
51
|
const tagsStates = ref<tagsFilterState.Storage>(new tagsFilterState.Storage());
|
|
38
52
|
|
|
53
|
+
provide("tagsStates", tagsStates);
|
|
54
|
+
|
|
39
55
|
const globalSettings = useGlobalSettingsStore();
|
|
40
56
|
|
|
41
57
|
globalSettings.mainPanelMode = e.MainPanelMode.Rules;
|
|
42
58
|
|
|
59
|
+
function goToNews() {
|
|
60
|
+
router.push({name: e.MainPanelMode.Entries, params: {}});
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
const rules = computedAsync(async () => {
|
|
44
64
|
// force refresh
|
|
45
65
|
globalSettings.dataVersion;
|
|
46
66
|
return await api.getRules();
|
|
47
67
|
}, null);
|
|
48
68
|
|
|
49
|
-
const
|
|
69
|
+
const tagsCount = computed(() => {
|
|
50
70
|
if (!rules.value) {
|
|
51
71
|
return {};
|
|
52
72
|
}
|
|
@@ -54,7 +74,11 @@
|
|
|
54
74
|
const tags: {[key: string]: number} = {};
|
|
55
75
|
|
|
56
76
|
for (const rule of rules.value) {
|
|
57
|
-
for (const tag of rule.
|
|
77
|
+
for (const tag of rule.requiredTags) {
|
|
78
|
+
tags[tag] = (tags[tag] || 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const tag of rule.excludedTags) {
|
|
58
82
|
tags[tag] = (tags[tag] || 0) + 1;
|
|
59
83
|
}
|
|
60
84
|
}
|
|
@@ -69,7 +93,7 @@
|
|
|
69
93
|
|
|
70
94
|
let sorted = rules.value.slice();
|
|
71
95
|
|
|
72
|
-
sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.
|
|
96
|
+
sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.requiredTags.concat(rule.excludedTags));
|
|
73
97
|
|
|
74
98
|
const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder);
|
|
75
99
|
|
|
@@ -86,14 +110,14 @@
|
|
|
86
110
|
|
|
87
111
|
sorted = sorted.sort((a: t.Rule, b: t.Rule) => {
|
|
88
112
|
if (globalSettings.rulesOrder === e.RulesOrder.Tags) {
|
|
89
|
-
return utils.compareLexicographically(a.
|
|
113
|
+
return utils.compareLexicographically(a.allTags, b.allTags);
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
const valueA = _.get(a, orderField, null);
|
|
93
117
|
const valueB = _.get(b, orderField, null);
|
|
94
118
|
|
|
95
119
|
if (valueA === null && valueB === null) {
|
|
96
|
-
return utils.compareLexicographically(a.
|
|
120
|
+
return utils.compareLexicographically(a.allTags, b.allTags);
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
if (valueA === null) {
|
|
@@ -112,13 +136,9 @@
|
|
|
112
136
|
return -1 * direction;
|
|
113
137
|
}
|
|
114
138
|
|
|
115
|
-
return utils.compareLexicographically(a.
|
|
139
|
+
return utils.compareLexicographically(a.allTags, b.allTags);
|
|
116
140
|
});
|
|
117
141
|
|
|
118
142
|
return sorted;
|
|
119
143
|
});
|
|
120
|
-
|
|
121
|
-
function onTagStateChanged({tag, state}: {tag: string; state: tagsFilterState.State}) {
|
|
122
|
-
tagsStates.value.onTagStateChanged({tag, state});
|
|
123
|
-
}
|
|
124
144
|
</script>
|