feeds-fun 1.17.1 → 1.18.1
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 +11 -0
- package/src/components/EntryForList.vue +17 -6
- package/src/components/RuleForList.vue +2 -2
- package/src/components/RulesList.vue +18 -9
- package/src/components/TokensCost.vue +0 -2
- package/src/components/collections/DetailedItem.vue +9 -0
- package/src/components/collections/PublicIntro.vue +63 -0
- package/src/components/collections/PublicSelector.vue +43 -0
- package/src/components/main/Block.vue +1 -1
- package/src/components/main/Item.vue +1 -1
- package/src/components/notifications/LoadedOldNews.vue +47 -0
- package/src/components/page_header/ExternalLinks.vue +11 -6
- package/src/components/side_pannel/CollapseButton.vue +56 -0
- package/src/components/tags/EntryTag.vue +4 -3
- package/src/components/tags/FilterTag.vue +6 -3
- package/src/components/tags/RuleTag.vue +4 -3
- package/src/components/tags/TagsFilter.vue +17 -5
- package/src/css/panels.css +5 -5
- package/src/css/side_panel_layout.css +4 -0
- package/src/layouts/SidePanelLayout.vue +51 -26
- package/src/layouts/WideLayout.vue +0 -4
- package/src/logic/api.ts +23 -0
- package/src/logic/enums.ts +28 -12
- package/src/logic/events.ts +56 -8
- package/src/logic/tagsFilterState.ts +61 -1
- package/src/logic/types.ts +15 -2
- package/src/logic/utils.ts +104 -1
- package/src/main.ts +10 -0
- package/src/router/index.ts +16 -3
- package/src/stores/collections.ts +17 -1
- package/src/stores/entries.ts +154 -19
- package/src/stores/globalSettings.ts +11 -8
- package/src/views/AuthView.vue +3 -1
- package/src/views/CollectionsView.vue +13 -2
- package/src/views/DiscoveryView.vue +3 -1
- package/src/views/FeedsView.vue +3 -1
- package/src/views/MainView.vue +18 -1
- package/src/views/NewsView.vue +36 -97
- package/src/views/PublicCollectionView.vue +237 -0
- package/src/views/RulesView.vue +31 -29
- package/src/views/SettingsView.vue +3 -1
package/src/views/NewsView.vue
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
<template #side-menu-item-2>
|
|
11
11
|
Sort by
|
|
12
|
+
<!-- Remember that we should use entriesStore.activeOrderProperties everywhere in the News view-->
|
|
13
|
+
<!-- Here, It should be in sync with this selector (and globalSettings.entriesOrder) on the NewsView, -->
|
|
14
|
+
<!-- so, it should work fine -->
|
|
12
15
|
<config-selector
|
|
13
16
|
:values="e.EntriesOrderProperties"
|
|
14
17
|
v-model:property="globalSettings.entriesOrder" />
|
|
@@ -35,8 +38,7 @@
|
|
|
35
38
|
<template #side-footer>
|
|
36
39
|
<tags-filter
|
|
37
40
|
:tags="tagsCount"
|
|
38
|
-
:show-create-rule="true"
|
|
39
|
-
change-source="news_tags_filter" />
|
|
41
|
+
:show-create-rule="true" />
|
|
40
42
|
</template>
|
|
41
43
|
|
|
42
44
|
<template #main-header>
|
|
@@ -53,10 +55,16 @@
|
|
|
53
55
|
:collections-notification_="!hasEntries"
|
|
54
56
|
:collections-warning_="false" />
|
|
55
57
|
|
|
58
|
+
<notifications-loaded-old-news
|
|
59
|
+
:entries="entriesStore.loadedEntriesReport || []"
|
|
60
|
+
:period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod)" />
|
|
61
|
+
|
|
56
62
|
<entries-list
|
|
63
|
+
:loading="entriesStore.loading"
|
|
57
64
|
:entriesIds="entriesReport"
|
|
58
|
-
:time-field="timeField"
|
|
65
|
+
:time-field="entriesStore.activeOrderProperties.timeField"
|
|
59
66
|
:tags-count="tagsCount"
|
|
67
|
+
:show-score="true"
|
|
60
68
|
:showFromStart="25"
|
|
61
69
|
:showPerPage="25" />
|
|
62
70
|
</side-panel-layout>
|
|
@@ -64,10 +72,12 @@
|
|
|
64
72
|
|
|
65
73
|
<script lang="ts" setup>
|
|
66
74
|
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
75
|
+
import {useRoute, useRouter} from "vue-router";
|
|
67
76
|
import {computedAsync} from "@vueuse/core";
|
|
68
77
|
import * as api from "@/logic/api";
|
|
69
78
|
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
70
79
|
import * as e from "@/logic/enums";
|
|
80
|
+
import * as utils from "@/logic/utils";
|
|
71
81
|
import type * as t from "@/logic/types";
|
|
72
82
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
73
83
|
import {useEntriesStore} from "@/stores/entries";
|
|
@@ -76,107 +86,42 @@
|
|
|
76
86
|
const globalSettings = useGlobalSettingsStore();
|
|
77
87
|
const entriesStore = useEntriesStore();
|
|
78
88
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
provide("tagsStates", tagsStates);
|
|
82
|
-
|
|
83
|
-
globalSettings.mainPanelMode = e.MainPanelMode.Entries;
|
|
84
|
-
|
|
85
|
-
globalSettings.updateDataVersion();
|
|
86
|
-
|
|
87
|
-
const entriesWithOpenedBody = ref<{[key: t.EntryId]: boolean}>({});
|
|
88
|
-
|
|
89
|
-
const _sortedEntries = computed(() => {
|
|
90
|
-
if (entriesStore.loadedEntriesReport === null) {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
|
|
95
|
-
|
|
96
|
-
if (orderProperties === undefined) {
|
|
97
|
-
throw new Error(`Unknown order ${globalSettings.entriesOrder}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const field = orderProperties.orderField;
|
|
101
|
-
const direction = orderProperties.direction;
|
|
102
|
-
|
|
103
|
-
// let report = entriesStore.loadedEntriesReport.slice();
|
|
104
|
-
|
|
105
|
-
// Pre-map to avoid repeated lookups in the comparator
|
|
106
|
-
const mapped = entriesStore.loadedEntriesReport.map((entryId) => {
|
|
107
|
-
// @ts-ignore
|
|
108
|
-
return {entryId, value: entriesStore.entries[entryId][field]};
|
|
109
|
-
});
|
|
89
|
+
const route = useRoute();
|
|
90
|
+
const router = useRouter();
|
|
110
91
|
|
|
111
|
-
|
|
112
|
-
if (a.value === null && b.value === null) {
|
|
113
|
-
return 0;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (a.value === null) {
|
|
117
|
-
return 1;
|
|
118
|
-
}
|
|
92
|
+
entriesStore.setNewsMode();
|
|
119
93
|
|
|
120
|
-
|
|
121
|
-
return -1;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (a.value < b.value) {
|
|
125
|
-
return direction;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (a.value > b.value) {
|
|
129
|
-
return -direction;
|
|
130
|
-
}
|
|
94
|
+
const tagsStates = ref<tagsFilterState.Storage>(new tagsFilterState.Storage());
|
|
131
95
|
|
|
132
|
-
|
|
133
|
-
|
|
96
|
+
provide("tagsStates", tagsStates);
|
|
97
|
+
provide("eventsViewName", "news");
|
|
134
98
|
|
|
135
|
-
|
|
99
|
+
tagsFilterState.setSyncingTagsWithRoute({
|
|
100
|
+
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
101
|
+
route,
|
|
102
|
+
router
|
|
103
|
+
});
|
|
136
104
|
|
|
137
|
-
|
|
105
|
+
tagsFilterState.setSyncingTagsSidebarPoint({
|
|
106
|
+
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
107
|
+
globalSettings
|
|
138
108
|
});
|
|
139
109
|
|
|
140
|
-
|
|
141
|
-
let report = _sortedEntries.value.slice();
|
|
142
|
-
|
|
143
|
-
if (!globalSettings.showRead) {
|
|
144
|
-
report = report.filter((entryId) => {
|
|
145
|
-
if (entriesStore.displayedEntryId == entryId) {
|
|
146
|
-
// always show read entries with open body
|
|
147
|
-
// otherwise, they will hide right after opening it
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
return !entriesStore.entries[entryId].hasMarker(e.Marker.Read);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
110
|
+
globalSettings.mainPanelMode = e.MainPanelMode.Entries;
|
|
153
111
|
|
|
154
|
-
|
|
155
|
-
});
|
|
112
|
+
globalSettings.updateDataVersion();
|
|
156
113
|
|
|
157
114
|
const entriesReport = computed(() => {
|
|
158
|
-
let report =
|
|
115
|
+
let report = entriesStore.visibleEntries.slice();
|
|
159
116
|
|
|
160
117
|
report = tagsStates.value.filterByTags(report, (entryId) => entriesStore.entries[entryId].tags);
|
|
161
118
|
return report;
|
|
162
119
|
});
|
|
163
120
|
|
|
164
121
|
const tagsCount = computed(() => {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
for (const entryId of entriesReport.value) {
|
|
168
|
-
const entry = entriesStore.entries[entryId];
|
|
122
|
+
const entriesToProcess = entriesReport.value.map((entryId) => entriesStore.entries[entryId]);
|
|
169
123
|
|
|
170
|
-
|
|
171
|
-
if (tag in tagsCount) {
|
|
172
|
-
tagsCount[tag] += 1;
|
|
173
|
-
} else {
|
|
174
|
-
tagsCount[tag] = 1;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return tagsCount;
|
|
124
|
+
return utils.countTags(entriesToProcess);
|
|
180
125
|
});
|
|
181
126
|
|
|
182
127
|
const entriesNumber = computed(() => {
|
|
@@ -188,6 +133,10 @@
|
|
|
188
133
|
});
|
|
189
134
|
|
|
190
135
|
const hasRules = computed(() => {
|
|
136
|
+
if (entriesStore.loading) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
191
140
|
if (entriesStore.loadedEntriesReport === null) {
|
|
192
141
|
return false;
|
|
193
142
|
}
|
|
@@ -201,16 +150,6 @@
|
|
|
201
150
|
}
|
|
202
151
|
return false;
|
|
203
152
|
});
|
|
204
|
-
|
|
205
|
-
const timeField = computed(() => {
|
|
206
|
-
const orderProperties = e.EntriesOrderProperties.get(globalSettings.entriesOrder);
|
|
207
|
-
|
|
208
|
-
if (orderProperties === undefined) {
|
|
209
|
-
throw new Error(`Unknown entries order: ${globalSettings.entriesOrder}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return orderProperties.timeField;
|
|
213
|
-
});
|
|
214
153
|
</script>
|
|
215
154
|
|
|
216
155
|
<style></style>
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<side-panel-layout
|
|
3
|
+
:reloadButton="false"
|
|
4
|
+
:login-required="false"
|
|
5
|
+
:home-button="true">
|
|
6
|
+
<template #side-menu-item-1>
|
|
7
|
+
<collections-public-selector
|
|
8
|
+
class="min-w-full"
|
|
9
|
+
v-if="collection"
|
|
10
|
+
:collection-id="collection.id" />
|
|
11
|
+
|
|
12
|
+
<p
|
|
13
|
+
v-if="collection"
|
|
14
|
+
class="ffun-info-common my-2"
|
|
15
|
+
>{{ collection.description }}</p
|
|
16
|
+
>
|
|
17
|
+
</template>
|
|
18
|
+
<template #side-menu-item-2>
|
|
19
|
+
For
|
|
20
|
+
<config-selector
|
|
21
|
+
:values="e.LastEntriesPeriodProperties"
|
|
22
|
+
v-model:property="globalSettings.lastEntriesPeriod" />
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<template #side-menu-item-3>
|
|
26
|
+
Show read
|
|
27
|
+
|
|
28
|
+
<config-flag
|
|
29
|
+
style="min-width: 2.5rem"
|
|
30
|
+
v-model:flag="globalSettings.showRead"
|
|
31
|
+
on-text="no"
|
|
32
|
+
off-text="yes" />
|
|
33
|
+
|
|
34
|
+
<button
|
|
35
|
+
class="ffun-form-button py-0 ml-1"
|
|
36
|
+
title='Undo last "mark read" operation'
|
|
37
|
+
:disabled="!entriesStore.canUndoMarkRead"
|
|
38
|
+
@click="entriesStore.undoMarkRead()">
|
|
39
|
+
↶
|
|
40
|
+
</button>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<template #side-footer>
|
|
44
|
+
<tags-filter
|
|
45
|
+
:tags="tagsCount"
|
|
46
|
+
:show-create-rule="false"
|
|
47
|
+
:show-registration-invitation="true" />
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template #main-header>
|
|
51
|
+
News
|
|
52
|
+
<span v-if="entriesNumber > 0">[{{ entriesNumber }}]</span>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template #main-footer> </template>
|
|
56
|
+
|
|
57
|
+
<!-- currently we have a "nuance" with tags user experience in this block -->
|
|
58
|
+
<!-- The tags work as expected till the user selects their own tags from other places -->
|
|
59
|
+
<!-- after that we can get a situation, when, after clicking on a tag in the block, there will be no news displayed -->
|
|
60
|
+
<!-- because the user previously selected tags that have no common news with the tags in the block -->
|
|
61
|
+
<!-- That effect is possible only if the user has already interacted with the ags filter => should not be a problem -->
|
|
62
|
+
<collections-public-intro
|
|
63
|
+
v-if="collection && !globalState.isLoggedIn"
|
|
64
|
+
:collectionId="collection.id"
|
|
65
|
+
:tag1Uid="medianTag1"
|
|
66
|
+
:tag1Count="tagsCount[medianTag1] || 0"
|
|
67
|
+
:tag2Uid="medianTag2"
|
|
68
|
+
:tag2Count="tagsCount[medianTag2] || 0" />
|
|
69
|
+
|
|
70
|
+
<div
|
|
71
|
+
v-if="collection && globalState.isLoggedIn"
|
|
72
|
+
class="ffun-info-good">
|
|
73
|
+
<p
|
|
74
|
+
>Welcome to curated <strong>{{ collection.name }}</strong> news collection!</p
|
|
75
|
+
>
|
|
76
|
+
|
|
77
|
+
<p>Collection news is always shown in the order of publication.</p>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<notifications-loaded-old-news
|
|
81
|
+
:entries="entriesStore.loadedEntriesReport || []"
|
|
82
|
+
:period="e.LastEntriesPeriodProperties.get(globalSettings.lastEntriesPeriod)" />
|
|
83
|
+
|
|
84
|
+
<entries-list
|
|
85
|
+
:loading="entriesStore.loading"
|
|
86
|
+
:entriesIds="entriesReport"
|
|
87
|
+
:time-field="entriesStore.activeOrderProperties.timeField"
|
|
88
|
+
:tags-count="tagsCount"
|
|
89
|
+
:show-score="false"
|
|
90
|
+
:showFromStart="25"
|
|
91
|
+
:showPerPage="25" />
|
|
92
|
+
</side-panel-layout>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script lang="ts" setup>
|
|
96
|
+
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
97
|
+
import type {ComputedRef} from "vue";
|
|
98
|
+
import {useRoute, useRouter} from "vue-router";
|
|
99
|
+
import {computedAsync} from "@vueuse/core";
|
|
100
|
+
import * as api from "@/logic/api";
|
|
101
|
+
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
102
|
+
import * as e from "@/logic/enums";
|
|
103
|
+
import * as utils from "@/logic/utils";
|
|
104
|
+
import type * as t from "@/logic/types";
|
|
105
|
+
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
106
|
+
import {useEntriesStore} from "@/stores/entries";
|
|
107
|
+
import {useCollectionsStore} from "@/stores/collections";
|
|
108
|
+
import {useGlobalState} from "@/stores/globalState";
|
|
109
|
+
import _ from "lodash";
|
|
110
|
+
import * as asserts from "@/logic/asserts";
|
|
111
|
+
|
|
112
|
+
const route = useRoute();
|
|
113
|
+
const router = useRouter();
|
|
114
|
+
|
|
115
|
+
const globalState = useGlobalState();
|
|
116
|
+
const globalSettings = useGlobalSettingsStore();
|
|
117
|
+
const entriesStore = useEntriesStore();
|
|
118
|
+
const collections = useCollectionsStore();
|
|
119
|
+
|
|
120
|
+
const collectionSlug = computed(() => route.params.collectionSlug as t.CollectionSlug);
|
|
121
|
+
|
|
122
|
+
const collection = computed(() => {
|
|
123
|
+
if (!collectionSlug.value) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = collections.getCollectionBySlug({slug: collectionSlug.value});
|
|
128
|
+
|
|
129
|
+
if (Object.keys(collections.collections).length > 0 && !result) {
|
|
130
|
+
// TODO: implement better behaviour for broken slugs
|
|
131
|
+
router.push({name: "main"});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const tagsStates = ref<tagsFilterState.Storage>(new tagsFilterState.Storage());
|
|
138
|
+
|
|
139
|
+
globalSettings.mainPanelMode = e.MainPanelMode.PublicCollection;
|
|
140
|
+
|
|
141
|
+
// Required to separate real collection change (and reset tags filter) from the collection initialization
|
|
142
|
+
const lastDefinedCollectionId = ref<t.CollectionId | null>(null);
|
|
143
|
+
|
|
144
|
+
watch(
|
|
145
|
+
collection,
|
|
146
|
+
() => {
|
|
147
|
+
if (!collection.value) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
entriesStore.setPublicCollectionMode(collection.value.slug);
|
|
152
|
+
|
|
153
|
+
if (lastDefinedCollectionId.value !== null && lastDefinedCollectionId.value !== collection.value.id) {
|
|
154
|
+
tagsStates.value.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (lastDefinedCollectionId.value !== collection.value.id) {
|
|
158
|
+
lastDefinedCollectionId.value = collection.value.id;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{immediate: true}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
provide("tagsStates", tagsStates);
|
|
165
|
+
provide("eventsViewName", "public_collections");
|
|
166
|
+
|
|
167
|
+
tagsFilterState.setSyncingTagsWithRoute({
|
|
168
|
+
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
169
|
+
route,
|
|
170
|
+
router
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
globalSettings.updateDataVersion();
|
|
174
|
+
|
|
175
|
+
const entriesReport = computed(() => {
|
|
176
|
+
let report = entriesStore.visibleEntries.slice();
|
|
177
|
+
|
|
178
|
+
report = tagsStates.value.filterByTags(report, (entryId) => entriesStore.entries[entryId].tags);
|
|
179
|
+
return report;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const tagsCount = computed(() => {
|
|
183
|
+
const entriesToProcess = entriesReport.value.map((entryId) => entriesStore.entries[entryId]);
|
|
184
|
+
|
|
185
|
+
return utils.countTags(entriesToProcess);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const entriesNumber = computed(() => {
|
|
189
|
+
return entriesReport.value.length;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const medianTag1: ComputedRef<string> = computed(() => {
|
|
193
|
+
// do not change tag when the filter changed
|
|
194
|
+
if (tagsStates.value.hasSelectedTags) {
|
|
195
|
+
return medianTag1.value;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const entriesNumber = entriesReport.value.length;
|
|
199
|
+
|
|
200
|
+
const result = utils.chooseTagByUsage({tagsCount: tagsCount.value, border: 0.5 * entriesNumber, exclude: []});
|
|
201
|
+
|
|
202
|
+
if (result === null) {
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const medianTag2: ComputedRef<string> = computed(() => {
|
|
210
|
+
// do not change tag when the filter changed
|
|
211
|
+
if (tagsStates.value.hasSelectedTags) {
|
|
212
|
+
return medianTag2.value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const entriesToProcess = entriesReport.value
|
|
216
|
+
.map((entryId) => entriesStore.entries[entryId])
|
|
217
|
+
.filter((entry) => entry.tags.includes(medianTag1.value));
|
|
218
|
+
|
|
219
|
+
const entriesNumber = entriesToProcess.length;
|
|
220
|
+
|
|
221
|
+
const counts = utils.countTags(entriesToProcess);
|
|
222
|
+
|
|
223
|
+
const result = utils.chooseTagByUsage({
|
|
224
|
+
tagsCount: counts,
|
|
225
|
+
border: 0.5 * entriesNumber,
|
|
226
|
+
exclude: [medianTag1.value]
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (result === null) {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
});
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<style></style>
|
package/src/views/RulesView.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<side-panel-layout :reload-button="true">
|
|
3
3
|
<template #main-header>
|
|
4
4
|
Rules
|
|
5
|
-
<span v-if="rules">[{{
|
|
5
|
+
<span v-if="rules">[{{ rulesNumber }}]</span>
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<template #side-menu-item-2>
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
<template #side-footer>
|
|
16
16
|
<tags-filter
|
|
17
17
|
:tags="tagsCount"
|
|
18
|
-
:show-create-rule="false"
|
|
19
|
-
change-source="rules_tags_filter" />
|
|
18
|
+
:show-create-rule="false" />
|
|
20
19
|
</template>
|
|
21
20
|
|
|
22
21
|
<div class="ffun-info-common mb-2">
|
|
@@ -32,14 +31,14 @@
|
|
|
32
31
|
</div>
|
|
33
32
|
|
|
34
33
|
<rules-list
|
|
35
|
-
|
|
34
|
+
:loading="loading"
|
|
36
35
|
:rules="sortedRules" />
|
|
37
36
|
</side-panel-layout>
|
|
38
37
|
</template>
|
|
39
38
|
|
|
40
39
|
<script lang="ts" setup>
|
|
41
40
|
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
42
|
-
import {useRouter} from "vue-router";
|
|
41
|
+
import {useRoute, useRouter} from "vue-router";
|
|
43
42
|
import {computedAsync} from "@vueuse/core";
|
|
44
43
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
45
44
|
import _ from "lodash";
|
|
@@ -49,46 +48,41 @@
|
|
|
49
48
|
import * as e from "@/logic/enums";
|
|
50
49
|
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
51
50
|
|
|
51
|
+
const route = useRoute();
|
|
52
52
|
const router = useRouter();
|
|
53
53
|
|
|
54
54
|
const tagsStates = ref<tagsFilterState.Storage>(new tagsFilterState.Storage());
|
|
55
55
|
|
|
56
56
|
provide("tagsStates", tagsStates);
|
|
57
|
+
provide("eventsViewName", "rules");
|
|
57
58
|
|
|
58
59
|
const globalSettings = useGlobalSettingsStore();
|
|
59
60
|
|
|
61
|
+
tagsFilterState.setSyncingTagsWithRoute({
|
|
62
|
+
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
63
|
+
route,
|
|
64
|
+
router
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
tagsFilterState.setSyncingTagsSidebarPoint({
|
|
68
|
+
tagsStates: tagsStates.value as unknown as tagsFilterState.Storage,
|
|
69
|
+
globalSettings
|
|
70
|
+
});
|
|
71
|
+
|
|
60
72
|
globalSettings.mainPanelMode = e.MainPanelMode.Rules;
|
|
61
73
|
|
|
62
74
|
function goToNews() {
|
|
63
75
|
router.push({name: e.MainPanelMode.Entries, params: {}});
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
const loading = computed(() => rules.value === null);
|
|
79
|
+
|
|
66
80
|
const rules = computedAsync(async () => {
|
|
67
81
|
// force refresh
|
|
68
82
|
globalSettings.dataVersion;
|
|
69
83
|
return await api.getRules();
|
|
70
84
|
}, null);
|
|
71
85
|
|
|
72
|
-
const tagsCount = computed(() => {
|
|
73
|
-
if (!rules.value) {
|
|
74
|
-
return {};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const tags: {[key: string]: number} = {};
|
|
78
|
-
|
|
79
|
-
for (const rule of rules.value) {
|
|
80
|
-
for (const tag of rule.requiredTags) {
|
|
81
|
-
tags[tag] = (tags[tag] || 0) + 1;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const tag of rule.excludedTags) {
|
|
85
|
-
tags[tag] = (tags[tag] || 0) + 1;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return tags;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
86
|
const sortedRules = computed(() => {
|
|
93
87
|
if (!rules.value) {
|
|
94
88
|
return null;
|
|
@@ -96,7 +90,7 @@
|
|
|
96
90
|
|
|
97
91
|
let sorted = rules.value.slice();
|
|
98
92
|
|
|
99
|
-
sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.
|
|
93
|
+
sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.tags);
|
|
100
94
|
|
|
101
95
|
const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder);
|
|
102
96
|
|
|
@@ -113,14 +107,14 @@
|
|
|
113
107
|
|
|
114
108
|
sorted = sorted.sort((a: t.Rule, b: t.Rule) => {
|
|
115
109
|
if (globalSettings.rulesOrder === e.RulesOrder.Tags) {
|
|
116
|
-
return utils.compareLexicographically(a.
|
|
110
|
+
return utils.compareLexicographically(a.tags, b.tags);
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
const valueA = _.get(a, orderField, null);
|
|
120
114
|
const valueB = _.get(b, orderField, null);
|
|
121
115
|
|
|
122
116
|
if (valueA === null && valueB === null) {
|
|
123
|
-
return utils.compareLexicographically(a.
|
|
117
|
+
return utils.compareLexicographically(a.tags, b.tags);
|
|
124
118
|
}
|
|
125
119
|
|
|
126
120
|
if (valueA === null) {
|
|
@@ -139,9 +133,17 @@
|
|
|
139
133
|
return -1 * direction;
|
|
140
134
|
}
|
|
141
135
|
|
|
142
|
-
return utils.compareLexicographically(a.
|
|
136
|
+
return utils.compareLexicographically(a.tags, b.tags);
|
|
143
137
|
});
|
|
144
138
|
|
|
145
139
|
return sorted;
|
|
146
140
|
});
|
|
141
|
+
|
|
142
|
+
const rulesNumber = computed(() => {
|
|
143
|
+
return sortedRules.value ? sortedRules.value.length : 0;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const tagsCount = computed(() => {
|
|
147
|
+
return utils.countTags(sortedRules.value);
|
|
148
|
+
});
|
|
147
149
|
</script>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</template>
|
|
121
121
|
|
|
122
122
|
<script lang="ts" setup>
|
|
123
|
-
import {computed, ref, onUnmounted, watch} from "vue";
|
|
123
|
+
import {computed, ref, onUnmounted, watch, provide} from "vue";
|
|
124
124
|
import {computedAsync} from "@vueuse/core";
|
|
125
125
|
import * as api from "@/logic/api";
|
|
126
126
|
import * as t from "@/logic/types";
|
|
@@ -130,6 +130,8 @@
|
|
|
130
130
|
|
|
131
131
|
const globalSettings = useGlobalSettingsStore();
|
|
132
132
|
|
|
133
|
+
provide("eventsViewName", "settings");
|
|
134
|
+
|
|
133
135
|
globalSettings.mainPanelMode = e.MainPanelMode.Settings;
|
|
134
136
|
|
|
135
137
|
const tokensCostData = computedAsync(async () => {
|