feeds-fun 1.15.0 → 1.16.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 +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
|
@@ -1,72 +1,53 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div class="text-
|
|
3
|
+
<div class="text-base">
|
|
4
4
|
<ffun-tag
|
|
5
5
|
v-for="tag of displayedTags"
|
|
6
6
|
:key="tag"
|
|
7
7
|
:uid="tag"
|
|
8
|
-
:mode="tagMode(tag)"
|
|
9
8
|
:count="tagsCount[tag]"
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
:secondary-mode="tagMode(tag)"
|
|
10
|
+
count-mode="tooltip" />
|
|
12
11
|
|
|
13
12
|
<a
|
|
14
|
-
class="
|
|
13
|
+
class=""
|
|
14
|
+
title="Click on the news title to open it and see all tags"
|
|
15
15
|
href="#"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
>{{ tagsNumber - showLimit }} more</a
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
<a
|
|
22
|
-
class="ffun-normal-link"
|
|
23
|
-
href="#"
|
|
24
|
-
v-if="canHide"
|
|
25
|
-
@click.prevent="showAll = false"
|
|
26
|
-
>hide</a
|
|
16
|
+
@click.prevent="emit('request-to-show-all')"
|
|
17
|
+
v-if="!showAll && tagsNumber - showLimit > 0"
|
|
18
|
+
>[{{ tagsNumber - showLimit }} more]</a
|
|
27
19
|
>
|
|
28
20
|
</div>
|
|
29
|
-
|
|
30
|
-
<rule-constructor
|
|
31
|
-
v-if="selectedTagsList.length > 0"
|
|
32
|
-
:tags="selectedTagsList"
|
|
33
|
-
@rule-constructor:created="onRuleCreated" />
|
|
34
21
|
</div>
|
|
35
22
|
</template>
|
|
36
23
|
|
|
37
24
|
<script lang="ts" setup>
|
|
38
25
|
import {computed, ref} from "vue";
|
|
39
26
|
|
|
40
|
-
const
|
|
41
|
-
const showLimit = ref(5);
|
|
42
|
-
|
|
43
|
-
const selectedTags = ref<{[key: string]: boolean}>({});
|
|
27
|
+
const showLimit = 5;
|
|
44
28
|
|
|
45
29
|
const properties = defineProps<{
|
|
46
30
|
tags: string[];
|
|
47
31
|
contributions: {[key: string]: number};
|
|
48
32
|
tagsCount: {[key: string]: number};
|
|
33
|
+
showAll: boolean;
|
|
49
34
|
}>();
|
|
50
35
|
|
|
36
|
+
const emit = defineEmits(["request-to-show-all"]);
|
|
37
|
+
|
|
51
38
|
const tagsNumber = computed(() => {
|
|
52
39
|
return properties.tags.length;
|
|
53
40
|
});
|
|
54
41
|
|
|
55
42
|
const displayedTags = computed(() => {
|
|
56
|
-
if (showAll
|
|
43
|
+
if (properties.showAll) {
|
|
57
44
|
return preparedTags.value;
|
|
58
45
|
}
|
|
59
46
|
|
|
60
|
-
return preparedTags.value.slice(0, showLimit
|
|
47
|
+
return preparedTags.value.slice(0, showLimit);
|
|
61
48
|
});
|
|
62
49
|
|
|
63
50
|
function tagMode(tag: string) {
|
|
64
|
-
if (!!selectedTags.value[tag]) {
|
|
65
|
-
return "selected";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// return null;
|
|
69
|
-
|
|
70
51
|
if (!properties.contributions) {
|
|
71
52
|
return null;
|
|
72
53
|
}
|
|
@@ -129,37 +110,4 @@
|
|
|
129
110
|
|
|
130
111
|
return values;
|
|
131
112
|
});
|
|
132
|
-
|
|
133
|
-
const canShowAll = computed(() => {
|
|
134
|
-
return !showAll.value && showLimit.value < preparedTags.value.length;
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const canHide = computed(() => {
|
|
138
|
-
return showAll.value && showLimit.value < preparedTags.value.length;
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
function onTagClicked(tag: string) {
|
|
142
|
-
if (!!selectedTags.value[tag]) {
|
|
143
|
-
delete selectedTags.value[tag];
|
|
144
|
-
} else {
|
|
145
|
-
selectedTags.value[tag] = true;
|
|
146
|
-
showAll.value = true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const selectedTagsList = computed(() => {
|
|
151
|
-
const values = [];
|
|
152
|
-
|
|
153
|
-
for (const tag in selectedTags.value) {
|
|
154
|
-
values.push(tag);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
values.sort();
|
|
158
|
-
|
|
159
|
-
return values;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
function onRuleCreated() {
|
|
163
|
-
selectedTags.value = {};
|
|
164
|
-
}
|
|
165
113
|
</script>
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<script lang="ts" setup>
|
|
9
|
-
import {computed, ref, onUnmounted, watch} from "vue";
|
|
9
|
+
import {computed, ref, onUnmounted, watch, inject} from "vue";
|
|
10
|
+
import type {Ref} from "vue";
|
|
10
11
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
11
12
|
import {useCollectionsStore} from "@/stores/collections";
|
|
13
|
+
import * as tagsFilterState from "@/logic/tagsFilterState";
|
|
12
14
|
|
|
13
15
|
const properties = defineProps<{
|
|
14
16
|
apiKey: boolean;
|
|
@@ -20,6 +22,8 @@
|
|
|
20
22
|
const collections = useCollectionsStore();
|
|
21
23
|
const globalSettings = useGlobalSettingsStore();
|
|
22
24
|
|
|
25
|
+
const tagsStates = inject<Ref<tagsFilterState.Storage> | null>("tagsStates", null);
|
|
26
|
+
|
|
23
27
|
const showApiKeyMessage = computed(() => {
|
|
24
28
|
return (
|
|
25
29
|
globalSettings.userSettings &&
|
|
@@ -33,7 +37,8 @@
|
|
|
33
37
|
return (
|
|
34
38
|
properties.collectionsNotification_ &&
|
|
35
39
|
globalSettings.userSettings &&
|
|
36
|
-
!globalSettings.userSettings.hide_message_about_adding_collections.value
|
|
40
|
+
!globalSettings.userSettings.hide_message_about_adding_collections.value &&
|
|
41
|
+
!tagsStates?.value.hasSelectedTags
|
|
37
42
|
);
|
|
38
43
|
});
|
|
39
44
|
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="ffun-info-attention">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
<li>Click any tag
|
|
7
|
-
<li>
|
|
8
|
-
<li>
|
|
9
|
-
<li>
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<h3>Create your first news score rule</h3>
|
|
4
|
+
<p>Set up a rule to filter your news:</p>
|
|
5
|
+
<ol class="list-decimal list-inside">
|
|
6
|
+
<li><strong>Click</strong> any tag on a news item.</li>
|
|
7
|
+
<li>See how news is filtered by the chosen tag.</li>
|
|
8
|
+
<li>Locate the <strong>Create Rule</strong> panel in the left sidebar.</li>
|
|
9
|
+
<li>
|
|
10
|
+
Assign a numeric score for filtered news:
|
|
11
|
+
<ul>
|
|
12
|
+
<li>Use higher scores (above zero) for important or engaging content.</li>
|
|
13
|
+
<li>Use lower (below zero) scores for uninteresting or irrelevant content.</li>
|
|
14
|
+
</ul>
|
|
15
|
+
</li>
|
|
16
|
+
<li>Click <strong>Create Rule</strong>.</li>
|
|
17
|
+
<li>Watch the news reorganize based on your scores.</li>
|
|
18
|
+
</ol>
|
|
12
19
|
</div>
|
|
13
20
|
</template>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
v-for="score of scores"
|
|
8
8
|
:value="score"
|
|
9
9
|
:selected="modelValue === score">
|
|
10
|
-
{{ score }}
|
|
10
|
+
{{ addSign(score) }}
|
|
11
11
|
</option>
|
|
12
12
|
</select>
|
|
13
13
|
</template>
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const properties = withDefaults(defineProps<{scores?: number[]; modelValue: number}>(), {
|
|
20
20
|
scores: () => [
|
|
21
|
-
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,
|
|
22
|
-
-377, -610
|
|
21
|
+
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, -1, -2, -3, -5, -8, -13, -21, -34, -55, -89, -144, -233
|
|
23
22
|
],
|
|
24
23
|
modelValue: 1
|
|
25
24
|
});
|
|
@@ -32,6 +31,14 @@
|
|
|
32
31
|
const newScore = Number(target.value);
|
|
33
32
|
emit("update:modelValue", newScore);
|
|
34
33
|
}
|
|
34
|
+
|
|
35
|
+
function addSign(score: number) {
|
|
36
|
+
if (score > 0) {
|
|
37
|
+
return `+${score}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return score.toString();
|
|
41
|
+
}
|
|
35
42
|
</script>
|
|
36
43
|
|
|
37
44
|
<style scoped></style>
|
package/src/logic/api.ts
CHANGED
|
@@ -99,10 +99,22 @@ export async function getEntriesByIds({ids}: {ids: t.EntryId[]}) {
|
|
|
99
99
|
return entries;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export async function createOrUpdateRule({
|
|
102
|
+
export async function createOrUpdateRule({
|
|
103
|
+
requiredTags,
|
|
104
|
+
excludedTags,
|
|
105
|
+
score
|
|
106
|
+
}: {
|
|
107
|
+
requiredTags: string[];
|
|
108
|
+
excludedTags: string[];
|
|
109
|
+
score: number;
|
|
110
|
+
}) {
|
|
103
111
|
const response = await post({
|
|
104
112
|
url: API_CREATE_OR_UPDATE_RULE,
|
|
105
|
-
data: {
|
|
113
|
+
data: {
|
|
114
|
+
requiredTags: requiredTags,
|
|
115
|
+
excludedTags: excludedTags,
|
|
116
|
+
score: score
|
|
117
|
+
}
|
|
106
118
|
});
|
|
107
119
|
return response;
|
|
108
120
|
}
|
|
@@ -112,10 +124,20 @@ export async function deleteRule({id}: {id: t.RuleId}) {
|
|
|
112
124
|
return response;
|
|
113
125
|
}
|
|
114
126
|
|
|
115
|
-
export async function updateRule({
|
|
127
|
+
export async function updateRule({
|
|
128
|
+
id,
|
|
129
|
+
requiredTags,
|
|
130
|
+
excludedTags,
|
|
131
|
+
score
|
|
132
|
+
}: {
|
|
133
|
+
id: t.RuleId;
|
|
134
|
+
requiredTags: string[];
|
|
135
|
+
excludedTags: string[];
|
|
136
|
+
score: number;
|
|
137
|
+
}) {
|
|
116
138
|
const response = await post({
|
|
117
139
|
url: API_UPDATE_RULE,
|
|
118
|
-
data: {id: id,
|
|
140
|
+
data: {id: id, score: score, requiredTags: requiredTags, excludedTags: excludedTags}
|
|
119
141
|
});
|
|
120
142
|
return response;
|
|
121
143
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {ref, computed, reactive} from "vue";
|
|
2
|
+
import type {ComputedRef} from "vue";
|
|
3
|
+
|
|
1
4
|
export type State = "required" | "excluded" | "none";
|
|
2
5
|
|
|
3
6
|
interface ReturnTagsForEntity {
|
|
@@ -7,48 +10,97 @@ interface ReturnTagsForEntity {
|
|
|
7
10
|
export class Storage {
|
|
8
11
|
requiredTags: {[key: string]: boolean};
|
|
9
12
|
excludedTags: {[key: string]: boolean};
|
|
13
|
+
selectedTags: ComputedRef<{[key: string]: boolean}>;
|
|
14
|
+
hasSelectedTags: ComputedRef<boolean>;
|
|
10
15
|
|
|
11
16
|
constructor() {
|
|
12
|
-
this.requiredTags = {};
|
|
13
|
-
this.excludedTags = {};
|
|
17
|
+
this.requiredTags = reactive({});
|
|
18
|
+
this.excludedTags = reactive({});
|
|
19
|
+
|
|
20
|
+
this.selectedTags = computed(() => {
|
|
21
|
+
return {...this.requiredTags, ...this.excludedTags};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.hasSelectedTags = computed(() => {
|
|
25
|
+
return Object.keys(this.selectedTags.value).length > 0;
|
|
26
|
+
});
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
onTagStateChanged({tag, state}: {tag: string; state: State}) {
|
|
17
30
|
if (state === "required") {
|
|
18
31
|
this.requiredTags[tag] = true;
|
|
19
|
-
this.excludedTags[tag]
|
|
32
|
+
if (this.excludedTags[tag]) {
|
|
33
|
+
delete this.excludedTags[tag];
|
|
34
|
+
}
|
|
20
35
|
} else if (state === "excluded") {
|
|
21
36
|
this.excludedTags[tag] = true;
|
|
22
|
-
this.requiredTags[tag]
|
|
37
|
+
if (this.requiredTags[tag]) {
|
|
38
|
+
delete this.requiredTags[tag];
|
|
39
|
+
}
|
|
23
40
|
} else if (state === "none") {
|
|
24
|
-
this.
|
|
25
|
-
|
|
41
|
+
if (this.requiredTags[tag]) {
|
|
42
|
+
delete this.requiredTags[tag];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.excludedTags[tag]) {
|
|
46
|
+
delete this.excludedTags[tag];
|
|
47
|
+
}
|
|
26
48
|
} else {
|
|
27
49
|
throw new Error(`Unknown tag state: ${state}`);
|
|
28
50
|
}
|
|
29
51
|
}
|
|
30
52
|
|
|
53
|
+
onTagReversed({tag}: {tag: string}) {
|
|
54
|
+
if (!(tag in this.selectedTags)) {
|
|
55
|
+
this.onTagStateChanged({tag: tag, state: "required"});
|
|
56
|
+
} else if (this.requiredTags[tag]) {
|
|
57
|
+
this.onTagStateChanged({tag: tag, state: "excluded"});
|
|
58
|
+
} else if (this.excludedTags[tag]) {
|
|
59
|
+
this.onTagStateChanged({tag: tag, state: "required"});
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(`Unknown tag state: ${tag}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onTagClicked({tag}: {tag: string}) {
|
|
66
|
+
if (tag in this.selectedTags) {
|
|
67
|
+
this.onTagStateChanged({tag: tag, state: "none"});
|
|
68
|
+
} else {
|
|
69
|
+
this.onTagStateChanged({tag: tag, state: "required"});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
filterByTags(entities: any[], getTags: ReturnTagsForEntity) {
|
|
32
74
|
let report = entities.slice();
|
|
33
75
|
|
|
76
|
+
const requiredTags = Object.keys(this.requiredTags);
|
|
77
|
+
|
|
34
78
|
report = report.filter((entity) => {
|
|
35
79
|
for (const tag of getTags(entity)) {
|
|
36
80
|
if (this.excludedTags[tag]) {
|
|
37
81
|
return false;
|
|
38
82
|
}
|
|
39
83
|
}
|
|
40
|
-
return true;
|
|
41
|
-
});
|
|
42
84
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (this.requiredTags[tag] && !getTags(entity).includes(tag)) {
|
|
85
|
+
for (const tag of requiredTags) {
|
|
86
|
+
if (!getTags(entity).includes(tag)) {
|
|
46
87
|
return false;
|
|
47
88
|
}
|
|
48
89
|
}
|
|
90
|
+
|
|
49
91
|
return true;
|
|
50
92
|
});
|
|
51
93
|
|
|
52
94
|
return report;
|
|
53
95
|
}
|
|
96
|
+
|
|
97
|
+
clear() {
|
|
98
|
+
Object.keys(this.requiredTags).forEach((key) => {
|
|
99
|
+
delete this.requiredTags[key];
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
Object.keys(this.excludedTags).forEach((key) => {
|
|
103
|
+
delete this.excludedTags[key];
|
|
104
|
+
});
|
|
105
|
+
}
|
|
54
106
|
}
|
package/src/logic/types.ts
CHANGED
|
@@ -231,7 +231,9 @@ export function entryFromJSON(
|
|
|
231
231
|
|
|
232
232
|
export type Rule = {
|
|
233
233
|
readonly id: RuleId;
|
|
234
|
-
readonly
|
|
234
|
+
readonly requiredTags: string[];
|
|
235
|
+
readonly excludedTags: string[];
|
|
236
|
+
readonly allTags: string[];
|
|
235
237
|
readonly score: number;
|
|
236
238
|
readonly createdAt: Date;
|
|
237
239
|
readonly updatedAt: Date;
|
|
@@ -239,22 +241,27 @@ export type Rule = {
|
|
|
239
241
|
|
|
240
242
|
export function ruleFromJSON({
|
|
241
243
|
id,
|
|
242
|
-
|
|
244
|
+
requiredTags,
|
|
245
|
+
excludedTags,
|
|
243
246
|
score,
|
|
244
247
|
createdAt,
|
|
245
248
|
updatedAt
|
|
246
249
|
}: {
|
|
247
250
|
id: string;
|
|
248
|
-
|
|
251
|
+
requiredTags: string[];
|
|
252
|
+
excludedTags: string[];
|
|
249
253
|
score: number;
|
|
250
254
|
createdAt: string;
|
|
251
255
|
updatedAt: string;
|
|
252
256
|
}): Rule {
|
|
253
|
-
|
|
257
|
+
requiredTags = requiredTags.sort();
|
|
258
|
+
excludedTags = excludedTags.sort();
|
|
254
259
|
|
|
255
260
|
return {
|
|
256
261
|
id: toRuleId(id),
|
|
257
|
-
|
|
262
|
+
requiredTags: requiredTags,
|
|
263
|
+
excludedTags: excludedTags,
|
|
264
|
+
allTags: requiredTags.concat(excludedTags),
|
|
258
265
|
score: score,
|
|
259
266
|
createdAt: new Date(createdAt),
|
|
260
267
|
updatedAt: new Date(updatedAt)
|
package/src/stores/entries.ts
CHANGED
|
@@ -8,12 +8,14 @@ import * as api from "@/logic/api";
|
|
|
8
8
|
import {Timer} from "@/logic/timer";
|
|
9
9
|
import {computedAsync} from "@vueuse/core";
|
|
10
10
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
11
|
+
import * as events from "@/logic/events";
|
|
11
12
|
|
|
12
13
|
export const useEntriesStore = defineStore("entriesStore", () => {
|
|
13
14
|
const globalSettings = useGlobalSettingsStore();
|
|
14
15
|
|
|
15
16
|
const entries = ref<{[key: t.EntryId]: t.Entry}>({});
|
|
16
17
|
const requestedEntries = ref<{[key: t.EntryId]: boolean}>({});
|
|
18
|
+
const displayedEntryId = ref<t.EntryId | null>(null);
|
|
17
19
|
|
|
18
20
|
function registerEntry(entry: t.Entry) {
|
|
19
21
|
if (entry.id in entries.value) {
|
|
@@ -95,11 +97,35 @@ export const useEntriesStore = defineStore("entriesStore", () => {
|
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
async function displayEntry({entryId}: {entryId: t.EntryId}) {
|
|
101
|
+
displayedEntryId.value = entryId;
|
|
102
|
+
|
|
103
|
+
requestFullEntry({entryId: entryId});
|
|
104
|
+
|
|
105
|
+
if (!entries.value[entryId].hasMarker(e.Marker.Read)) {
|
|
106
|
+
await setMarker({
|
|
107
|
+
entryId: entryId,
|
|
108
|
+
marker: e.Marker.Read
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await events.newsBodyOpened({entryId: entryId});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function hideEntry({entryId}: {entryId: t.EntryId}) {
|
|
116
|
+
if (displayedEntryId.value === entryId) {
|
|
117
|
+
displayedEntryId.value = null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
return {
|
|
99
122
|
entries,
|
|
100
123
|
requestFullEntry,
|
|
101
124
|
setMarker,
|
|
102
125
|
removeMarker,
|
|
103
|
-
loadedEntriesReport
|
|
126
|
+
loadedEntriesReport,
|
|
127
|
+
displayedEntryId,
|
|
128
|
+
displayEntry,
|
|
129
|
+
hideEntry
|
|
104
130
|
};
|
|
105
131
|
});
|
|
@@ -18,7 +18,6 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
18
18
|
// Entries
|
|
19
19
|
const lastEntriesPeriod = ref(e.LastEntriesPeriod.Day3);
|
|
20
20
|
const entriesOrder = ref(e.EntriesOrder.Score);
|
|
21
|
-
const showEntriesTags = ref(true);
|
|
22
21
|
const showRead = ref(true);
|
|
23
22
|
|
|
24
23
|
// Feeds
|
|
@@ -64,7 +63,6 @@ export const useGlobalSettingsStore = defineStore("globalSettings", () => {
|
|
|
64
63
|
mainPanelMode,
|
|
65
64
|
lastEntriesPeriod,
|
|
66
65
|
entriesOrder,
|
|
67
|
-
showEntriesTags,
|
|
68
66
|
showRead,
|
|
69
67
|
dataVersion,
|
|
70
68
|
updateDataVersion,
|
package/src/values/Score.vue
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
for (const rule of rules) {
|
|
31
31
|
const tags = [];
|
|
32
32
|
|
|
33
|
-
for (const tagId of rule.
|
|
33
|
+
for (const tagId of rule.requiredTags) {
|
|
34
34
|
const tagInfo = tagsStore.tags[tagId];
|
|
35
35
|
if (tagInfo) {
|
|
36
36
|
tags.push(tagInfo.name);
|
|
@@ -39,7 +39,16 @@
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
for (const tagId of rule.excludedTags) {
|
|
43
|
+
const tagInfo = tagsStore.tags[tagId];
|
|
44
|
+
if (tagInfo) {
|
|
45
|
+
tags.push("NOT " + tagInfo.name);
|
|
46
|
+
} else {
|
|
47
|
+
tags.push("NOT " + tagId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
strings.push(rule.score.toString().padStart(2, " ") + " — " + tags.join(" AND "));
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
alert(strings.join("\n"));
|
package/src/views/FeedsView.vue
CHANGED
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
off-text="last" />
|
|
26
26
|
</template>
|
|
27
27
|
|
|
28
|
+
<template #side-menu-item-4>
|
|
29
|
+
<a
|
|
30
|
+
class="ffun-form-button p-1 my-1 block w-full text-center"
|
|
31
|
+
href="/api/get-opml"
|
|
32
|
+
target="_blank"
|
|
33
|
+
>Download OPML</a
|
|
34
|
+
>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
28
37
|
<template #main-header>
|
|
29
38
|
Feeds
|
|
30
39
|
<span v-if="sortedFeeds"> [{{ sortedFeeds.length }}] </span>
|