feeds-fun 1.16.5 → 1.17.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.
Files changed (65) hide show
  1. package/package.json +1 -1
  2. package/src/components/DiscoveryForm.vue +3 -5
  3. package/src/components/EntriesList.vue +1 -11
  4. package/src/components/EntryForList.vue +54 -67
  5. package/src/components/FeedForList.vue +29 -64
  6. package/src/components/FeedInfo.vue +12 -14
  7. package/src/components/FeedsList.vue +2 -1
  8. package/src/components/OPMLUpload.vue +3 -3
  9. package/src/components/RuleConstructor.vue +12 -6
  10. package/src/components/RuleForList.vue +25 -24
  11. package/src/components/RulesList.vue +2 -1
  12. package/src/components/SimplePagination.vue +2 -2
  13. package/src/components/SupertokensLogin.vue +5 -6
  14. package/src/components/UserSetting.vue +7 -12
  15. package/src/components/UserSettingForNotification.vue +4 -3
  16. package/src/components/body_list/EntryBody.vue +43 -0
  17. package/src/components/body_list/FaviconColumn.vue +24 -0
  18. package/src/components/body_list/ReverseTimeColumn.vue +28 -0
  19. package/src/components/collections/Block.vue +1 -1
  20. package/src/components/collections/BlockItem.vue +4 -3
  21. package/src/components/collections/DetailedItem.vue +4 -10
  22. package/src/components/collections/FeedItem.vue +31 -24
  23. package/src/components/collections/Notification.vue +6 -8
  24. package/src/components/collections/SubscribingProgress.vue +14 -17
  25. package/src/components/collections/Warning.vue +36 -38
  26. package/src/components/main/Block.vue +5 -0
  27. package/src/components/main/Description.vue +51 -0
  28. package/src/components/main/HeaderLine.vue +7 -0
  29. package/src/components/main/Item.vue +27 -0
  30. package/src/components/main/NewsTitle.vue +26 -0
  31. package/src/components/notifications/ApiKey.vue +14 -5
  32. package/src/components/notifications/CreateRuleHelp.vue +10 -11
  33. package/src/components/page_header/ExternalLinks.vue +58 -0
  34. package/src/components/tags/Base.vue +28 -0
  35. package/src/components/tags/EntryTag.vue +73 -0
  36. package/src/components/{TagsList.vue → tags/EntryTagsList.vue} +8 -9
  37. package/src/components/tags/FakeTag.vue +35 -0
  38. package/src/components/tags/FilterTag.vue +114 -0
  39. package/src/components/tags/RuleTag.vue +67 -0
  40. package/src/components/{TagsFilter.vue → tags/TagsFilter.vue} +13 -7
  41. package/src/css/base.css +38 -0
  42. package/src/css/inputs.css +49 -0
  43. package/src/css/page_header.css +34 -0
  44. package/src/css/panels.css +49 -0
  45. package/src/css/side_panel_layout.css +34 -0
  46. package/src/css/tags.css +44 -0
  47. package/src/layouts/SidePanelLayout.vue +35 -91
  48. package/src/layouts/WideLayout.vue +5 -17
  49. package/src/logic/events.ts +23 -0
  50. package/src/logic/tagsFilterState.ts +14 -6
  51. package/src/logic/types.ts +16 -7
  52. package/src/logic/utils.ts +29 -0
  53. package/src/main.ts +42 -10
  54. package/src/style.css +10 -100
  55. package/src/values/DateTime.vue +1 -1
  56. package/src/values/{URL.vue → ExternalUrl.vue} +7 -6
  57. package/src/views/CollectionsView.vue +3 -6
  58. package/src/views/DiscoveryView.vue +9 -11
  59. package/src/views/FeedsView.vue +3 -2
  60. package/src/views/MainView.vue +189 -44
  61. package/src/views/NewsView.vue +2 -1
  62. package/src/views/RulesView.vue +6 -3
  63. package/src/views/SettingsView.vue +92 -33
  64. package/src/components/FfunTag.vue +0 -122
  65. package/src/inputs/Marker.vue +0 -54
@@ -1,24 +1,25 @@
1
1
  <template>
2
2
  <a
3
- :href="value"
3
+ :href="url"
4
4
  target="_blank"
5
- rel="noopener noreferrer"
6
- >{{ renderedText }}</a
7
- >
5
+ rel="noopener noreferrer">
6
+ {{ renderedText }}
7
+ <i class="ti ti-external-link" />
8
+ </a>
8
9
  </template>
9
10
 
10
11
  <script lang="ts" setup>
11
12
  import {computed} from "vue";
12
13
  import type * as t from "@/logic/types";
13
14
 
14
- const properties = defineProps<{value: t.URL; text?: string | null}>();
15
+ const properties = defineProps<{url: t.URL; text?: string | null}>();
15
16
 
16
17
  const renderedText = computed(() => {
17
18
  if (properties.text) {
18
19
  return properties.text;
19
20
  }
20
21
 
21
- return properties.value;
22
+ return properties.url;
22
23
  });
23
24
  </script>
24
25
 
@@ -2,12 +2,9 @@
2
2
  <side-panel-layout :reload-button="false">
3
3
  <template #main-header> Collections </template>
4
4
 
5
- <div class="ffun-info-attention">
6
- <p
7
- >We've prepared some ready-to-use thematic collections just for you. All the feeds in them are fully tagged
8
- free of charge.</p
9
- >
10
- <p>Subscribe to some and enjoy the full power of Feeds Fun!</p>
5
+ <div class="ffun-info-common">
6
+ <p>We've prepared thematic collections just for you.</p>
7
+ <p>News from collections are always tagged, ensuring you get the full power of Feeds Fun!</p>
11
8
  </div>
12
9
 
13
10
  <div
@@ -8,27 +8,25 @@
8
8
  :collections-notification_="false"
9
9
  :collections-warning_="true" />
10
10
 
11
- <h2>Load feeds from an OPML file</h2>
11
+ <h3>Load feeds from an OPML file</h3>
12
12
 
13
- <div class="ffun-info-good">
13
+ <div class="ffun-info-common">
14
14
  <p>
15
- <a
16
- href="https://en.wikipedia.org/wiki/OPML"
17
- target="_blank"
18
- >OPML</a
19
- >
15
+ <external-url
16
+ url="https://en.wikipedia.org/wiki/OPML"
17
+ text="OPML" />
20
18
  is a widely-used format for transferring news feed lists between platforms.
21
19
  </p>
22
20
 
23
21
  <p
24
- >Export your feeds from your old reader in OPML format and import them into our reader to seamlessly
25
- transition!</p
22
+ >Export your feeds from your old reader in OPML format and import them into Feeds Fun to seamlessly
23
+ transition.</p
26
24
  >
27
25
  </div>
28
26
 
29
- <opml-upload />
27
+ <opml-upload class="mt-4" />
30
28
 
31
- <h2>Search for a feed</h2>
29
+ <h3>Search for a feed</h3>
32
30
 
33
31
  <discovery-form />
34
32
  </side-panel-layout>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <side-panel-layout>
3
3
  <template #side-menu-item-1>
4
- Show descriptions:
4
+ Show descriptions
5
5
  <config-flag
6
6
  style="min-width: 2.5rem"
7
7
  v-model:flag="globalSettings.showFeedsDescriptions"
@@ -30,6 +30,7 @@
30
30
  class="ffun-form-button p-1 my-1 block w-full text-center"
31
31
  href="/api/get-opml"
32
32
  target="_blank"
33
+ title="Download OPML file with all your feeds"
33
34
  >Download OPML</a
34
35
  >
35
36
  </template>
@@ -44,7 +45,7 @@
44
45
  :create-rule-help="false"
45
46
  :api-key="false"
46
47
  :collections-notification_="sortedFeeds === null || sortedFeeds.length == 0"
47
- :collections-warning_="true" />
48
+ :collections-warning_="false" />
48
49
 
49
50
  <feeds-list
50
51
  v-if="sortedFeeds"
@@ -1,57 +1,202 @@
1
1
  <template>
2
2
  <wide-layout>
3
- <template #header>
4
- Feeds Fun
5
- <small class="block text-lg font-normal">Personalized news for professionals</small>
6
- </template>
3
+ <div class="ffun-page-header">
4
+ <div class="ffun-page-header-center-block">
5
+ <page-header-external-links :show-api="false" />
6
+ </div>
7
+ </div>
8
+
9
+ <hr />
10
+
11
+ <main-block>
12
+ <h1 class="m-0 text-5xl">Feeds Fun</h1>
13
+ <p class="mt-2 text-2xl">Transparent Personalized News</p>
14
+ </main-block>
15
+
16
+ <main-header-line>
17
+ <!-- TODO: uncomment this claim after we have some statistics on long-term users -->
18
+ <!-- Save over <strong class="text-green-700">80%</strong> of news-browsing time by focusing on what truly matters to you -->
19
+ Save news-browsing time by focusing on what truly matters
20
+ </main-header-line>
7
21
 
8
- <div class="max-w-lg mx-auto">
9
- <div class="ffun-info-good">
22
+ <main-block>
23
+ <div class="max-w-xl md:mx-auto ffun-info-good text-center mx-2">
10
24
  <supertokens-login />
11
25
  </div>
26
+ </main-block>
12
27
 
13
- <h2>News reader with tags</h2>
14
-
15
- <ul class="list-disc list-inside text-left">
16
- <li>Reader automatically assigns tags to news.</li>
17
- <li>You create rules to score news by tags.</li>
18
- <li>Filter and sort news how you want ⇒ read only what you want.</li>
19
- </ul>
20
-
21
- <h2>Curated collections of feeds</h2>
22
-
23
- <ul class="list-disc list-inside text-left">
24
- <li>We handpick the most interesting feeds into thematic collections.</li>
25
- <li>Feeds in the collections are tagged for free.</li>
26
- </ul>
27
-
28
- <h2>You are in control</h2>
29
-
30
- <ul class="list-disc list-inside text-left">
31
- <li
32
- ><a
33
- class="ffun-normal-link"
34
- :href="settings.githubRepo"
35
- target="_blank"
36
- >Open source</a
37
- >, self-hosted.</li
38
- >
39
- <li>No black box recommendation algorithms.</li>
40
- <li>No "smart" reordering.</li>
41
- <li>No ads.</li>
42
- <li>No selling of your data.</li>
43
- </ul>
44
-
45
- <h2>How it looks like</h2>
46
- </div>
28
+ <main-header-line> Smarter way to read news </main-header-line>
29
+
30
+ <main-block>
31
+ <main-description icon="ti-number-1">
32
+ <template #caption> Subscribe to sites </template>
33
+
34
+ <template #description>
35
+ <p class="text-lg font-medium">Give us the site URL and we do the rest.</p>
36
+ </template>
37
+ </main-description>
38
+
39
+ <main-description icon="ti-number-2">
40
+ <template #caption> Get automatic tagging </template>
41
+
42
+ <template #description>
43
+ <div>
44
+ <main-news-title
45
+ class=""
46
+ title="UFO crashes in Central Park"
47
+ :score="null" />
48
+
49
+ <fake-tag
50
+ uid="ufo"
51
+ name="ufo"
52
+ :link="null"
53
+ css-modifier="positive" />
54
+
55
+ <fake-tag
56
+ uid="news-dot-fake"
57
+ name="news.fake"
58
+ link="http://example.com"
59
+ css-modifier="negative" />
60
+
61
+ <fake-tag
62
+ uid="new-york"
63
+ name="new-york"
64
+ :link="null"
65
+ css-modifier="positive" />
66
+
67
+ <fake-tag
68
+ uid="space-exploration"
69
+ name="space-exploration"
70
+ :link="null"
71
+ css-modifier="positive" />
72
+ </div>
73
+ </template>
74
+ </main-description>
75
+
76
+ <main-description icon="ti-number-3">
77
+ <template #caption> Create scoring rules </template>
78
+
79
+ <template #description>
80
+ <div class="grid grid-cols-2 justify-items-start">
81
+ <div class="">
82
+ <fake-tag
83
+ uid="sci-fi"
84
+ name="sci-fi"
85
+ :link="null"
86
+ css-modifier="positive" />
87
+
88
+ <i class="ti ti-arrow-right"></i>
89
+
90
+ <span class="cursor-default text-purple-700 text-lg md:text-xl">+5</span>
91
+ </div>
92
+
93
+ <div class="">
94
+ <fake-tag
95
+ uid="news-dot-fake"
96
+ name="news.fake"
97
+ link="http://example.com"
98
+ css-modifier="negative" />
99
+
100
+ <i class="ti ti-arrow-right"></i>
47
101
 
48
- <img
49
- class="border-2 rounded border-slate-300"
50
- src="/news-filtering-example.png"
51
- alt="News filtering example" />
102
+ <span class="cursor-default text-purple-700 text-lg md:text-xl">-55</span>
103
+ </div>
104
+
105
+ <div class="">
106
+ <fake-tag
107
+ uid="ufo"
108
+ name="ufo"
109
+ :link="null"
110
+ css-modifier="positive" />
111
+
112
+ <i class="ti ti-plus"></i>
113
+
114
+ <fake-tag
115
+ uid="new-york"
116
+ name="new-york"
117
+ :link="null"
118
+ css-modifier="positive" />
119
+
120
+ <i class="ti ti-arrow-right"></i>
121
+
122
+ <span class="cursor-default-purple-700 text-lg md:text-xl">+8</span>
123
+ </div>
124
+
125
+ <div class="">
126
+ <fake-tag
127
+ uid="space-exploration"
128
+ name="space-exploration"
129
+ :link="null"
130
+ css-modifier="positive" />
131
+
132
+ <i class="ti ti-arrow-right"></i>
133
+
134
+ <span class="cursor-default text-purple-700 text-lg md:text-xl">+21</span>
135
+ </div>
136
+ </div>
137
+ </template>
138
+ </main-description>
139
+
140
+ <main-description icon="ti-number-4">
141
+ <template #caption> Read what matters </template>
142
+
143
+ <template #description>
144
+ <div class="justify-items-start">
145
+ <main-news-title
146
+ title="New mission on Mars"
147
+ :score="21" />
148
+
149
+ <main-news-title
150
+ class="opacity-75"
151
+ title="Sci-fi novel about UFO in New Yourk"
152
+ :score="13" />
153
+
154
+ <i class="opacity-65 block ti ti-dots justify-self-center"></i>
155
+
156
+ <main-news-title
157
+ class="opacity-55"
158
+ title="UFO crashes in Central Park"
159
+ :score="-26" />
160
+ </div>
161
+ </template>
162
+ </main-description>
163
+ </main-block>
164
+
165
+ <main-header-line> Curated news collections <br class="md:hidden" />for easy start </main-header-line>
166
+
167
+ <main-block>
168
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
169
+ <template
170
+ v-for="collectionId in collections.collectionsOrder"
171
+ :key="collectionId">
172
+ <main-item
173
+ v-if="collections.collections[collectionId].showOnMain"
174
+ icon="ti-arrow-narrow-right">
175
+ <template #caption>
176
+ {{ collections.collections[collectionId].name }}
177
+ </template>
178
+
179
+ <template #description>
180
+ {{ collections.collections[collectionId].description }}
181
+ </template>
182
+ </main-item>
183
+ </template>
184
+ </div>
185
+ </main-block>
186
+
187
+ <main-header-line> Here, take a peek </main-header-line>
188
+ <div class="text-center p-5">
189
+ <img
190
+ class="border-2 rounded border-slate-300 mx-auto"
191
+ src="/news-filtering-example.png"
192
+ alt="News filtering example" />
193
+ </div>
52
194
  </wide-layout>
53
195
  </template>
54
196
 
55
197
  <script lang="ts" setup>
56
198
  import * as settings from "@/logic/settings";
199
+ import {useCollectionsStore} from "@/stores/collections";
200
+
201
+ const collections = useCollectionsStore();
57
202
  </script>
@@ -35,7 +35,8 @@
35
35
  <template #side-footer>
36
36
  <tags-filter
37
37
  :tags="tagsCount"
38
- :show-create-rule="true" />
38
+ :show-create-rule="true"
39
+ change-source="news_tags_filter" />
39
40
  </template>
40
41
 
41
42
  <template #main-header>
@@ -13,10 +13,13 @@
13
13
  </template>
14
14
 
15
15
  <template #side-footer>
16
- <tags-filter :tags="tagsCount" />
16
+ <tags-filter
17
+ :tags="tagsCount"
18
+ :show-create-rule="false"
19
+ change-source="rules_tags_filter" />
17
20
  </template>
18
21
 
19
- <div class="ffun-info-good">
22
+ <div class="ffun-info-common mb-2">
20
23
  <p
21
24
  >You can create new rules on the
22
25
  <a
@@ -93,7 +96,7 @@
93
96
 
94
97
  let sorted = rules.value.slice();
95
98
 
96
- sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.requiredTags.concat(rule.excludedTags));
99
+ sorted = tagsStates.value.filterByTags(sorted, (rule) => rule.allTags);
97
100
 
98
101
  const orderProperties = e.RulesOrderProperties.get(globalSettings.rulesOrder);
99
102
 
@@ -2,35 +2,91 @@
2
2
  <side-panel-layout :reload-button="false">
3
3
  <template #main-header> Settings </template>
4
4
 
5
- <ul>
6
- <li>
7
- <strong class="mr-1">User id</strong>
8
- <input
9
- class="ffun-input w-72"
10
- disabled
11
- :value="userId" />
12
- </li>
13
- </ul>
14
-
15
- <hr />
16
-
17
- <template v-for="kind of settingsOrder">
18
- <user-setting :kind="kind" />
19
- <hr />
20
- </template>
5
+ <h3>General</h3>
6
+
7
+ <label class="mr-1">User id</label>
8
+ <input
9
+ class="ffun-input w-72 cursor-pointer"
10
+ disabled
11
+ :value="userId" />
12
+
13
+ <h3>Messages</h3>
14
+
15
+ <user-setting
16
+ v-for="kind of messagesSettings"
17
+ key="kind"
18
+ :kind="kind" />
19
+
20
+ <h3>Tagging</h3>
21
+
22
+ <div class="ffun-info-common">
23
+ <p>
24
+ All feeds from
25
+ <a
26
+ href="#"
27
+ @click.prevent="goToCollections()"
28
+ >collections</a
29
+ >
30
+ are tagged for free.
31
+ </p>
32
+
33
+ <p>
34
+ If you want to tag your own feeds, we kindly ask you to provide an
35
+ <external-url
36
+ url="https://platform.openai.com/docs/api-reference/introduction"
37
+ text="OpenAI" />
38
+ or
39
+ <external-url
40
+ url="https://ai.google.dev/gemini-api/docs/api-key"
41
+ text="Gemini" />
42
+ API key.
43
+ </p>
44
+
45
+ <p><strong>Here is how we use your API key:</strong></p>
46
+
47
+ <ul>
48
+ <li>We use your key only for your feeds that are not part of predefined collections.</li>
49
+ <li>We stop using your key when its usage exceeds the monthly limit you set.</li>
50
+ <li
51
+ >If a feed has multiple subscribers with API keys, we'll use a key with the lowest usage in the current
52
+ month.</li
53
+ >
54
+ <li>We do not process old news until you tell us to.</li>
55
+ </ul>
56
+
57
+ <p><strong>The more users set up an API key, the cheaper Feeds Fun becomes for everyone.</strong></p>
58
+
59
+ <p>API key usage statistics are available on this page.</p>
60
+ </div>
61
+
62
+ <user-setting
63
+ kind="openai_api_key"
64
+ class="mt-4" />
65
+ <user-setting kind="gemini_api_key" />
66
+ <user-setting kind="max_tokens_cost_in_month" />
67
+
68
+ <user-setting kind="process_entries_not_older_than" />
69
+
70
+ <div class="ffun-info-common mb-4">
71
+ <p>
72
+ The age of a news item is calculated based on the time it was published (according to the data in the feed).
73
+ </p>
74
+ </div>
21
75
 
22
76
  <h3>API usage</h3>
23
77
 
24
- <p>Estimated tokens cost for your API keys usage per month.</p>
78
+ <div class="ffun-info-common mb-4">
79
+ <p>Estimated tokens cost for your API keys usage per month.</p>
25
80
 
26
- <ul class="list-disc list-inside">
27
- <li> <strong>Estimated Used USD</strong> — the estimated cost of tokens in processed requests. </li>
28
- <li>
29
- <strong>Estimated Reserved USD</strong> — the estimated cost of tokens reserved for requests that currently are
30
- processing or were not processed correctly.
31
- </li>
32
- <li> <strong>Estimated Total USD</strong> — the estimated total cost of tokens used in the month. </li>
33
- </ul>
81
+ <ul class="list-disc list-inside">
82
+ <li> <strong>Estimated Used USD</strong> — the estimated cost of tokens in processed requests. </li>
83
+ <li>
84
+ <strong>Estimated Reserved USD</strong> — the estimated cost of tokens reserved for requests that are
85
+ currently processing or were not processed correctly.
86
+ </li>
87
+ <li> <strong>Estimated Total USD</strong> — the estimated total cost of tokens used in the month. </li>
88
+ </ul>
89
+ </div>
34
90
 
35
91
  <p v-if="tokensCostData == null">Loading...</p>
36
92
 
@@ -69,6 +125,7 @@
69
125
  import * as api from "@/logic/api";
70
126
  import * as t from "@/logic/types";
71
127
  import * as e from "@/logic/enums";
128
+ import {useRouter} from "vue-router";
72
129
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
73
130
 
74
131
  const globalSettings = useGlobalSettingsStore();
@@ -87,16 +144,18 @@
87
144
  return globalSettings.info.userId;
88
145
  });
89
146
 
90
- // TODO: refactor, this is the temporary code to display settings in the right order
91
- const settingsOrder = [
92
- "openai_api_key",
93
- "gemini_api_key",
94
- "max_tokens_cost_in_month",
95
- "process_entries_not_older_than",
147
+ const messagesSettings = [
96
148
  "hide_message_about_setting_up_key",
97
149
  "hide_message_about_adding_collections",
98
150
  "hide_message_check_your_feed_urls"
99
151
  ];
100
- </script>
101
152
 
102
- <style></style>
153
+ const router = useRouter();
154
+
155
+ function goToCollections() {
156
+ router.push({name: e.MainPanelMode.Collections, params: {}});
157
+ }
158
+
159
+ // TODO: check api keys on setup
160
+ // TODO: basic integer checks
161
+ </script>
@@ -1,122 +0,0 @@
1
- <template>
2
- <div class="inline-block">
3
- <a
4
- href="#"
5
- v-if="showSwitch"
6
- class="pr-1"
7
- @click.prevent="onRevers()"
8
- >⇄</a
9
- >
10
- <div
11
- :class="classes"
12
- :title="tooltip"
13
- @click.prevent="onClick()">
14
- <span v-if="countMode == 'prefix'">[{{ count }}]</span>
15
-
16
- {{ tagInfo.name }}
17
-
18
- <a
19
- v-if="tagInfo.link"
20
- :href="tagInfo.link"
21
- target="_blank"
22
- @click.stop=""
23
- rel="noopener noreferrer">
24
- &#8599;
25
- </a>
26
- </div>
27
- </div>
28
- </template>
29
-
30
- <script lang="ts" setup>
31
- import * as t from "@/logic/types";
32
- import {computed, ref, inject} from "vue";
33
- import type {Ref} from "vue";
34
- import {useTagsStore} from "@/stores/tags";
35
- import * as tagsFilterState from "@/logic/tagsFilterState";
36
- import * as asserts from "@/logic/asserts";
37
-
38
- const tagsStore = useTagsStore();
39
-
40
- const tagsStates = inject<Ref<tagsFilterState.Storage>>("tagsStates");
41
-
42
- asserts.defined(tagsStates);
43
-
44
- const properties = defineProps<{
45
- uid: string;
46
- count?: number | null;
47
- countMode?: string | null;
48
- secondaryMode?: string | null;
49
- showSwitch?: boolean | null;
50
- }>();
51
-
52
- const tagInfo = computed(() => {
53
- const tagInfo = tagsStore.tags[properties.uid];
54
-
55
- if (tagInfo) {
56
- return tagInfo;
57
- }
58
-
59
- tagsStore.requestTagInfo({tagUid: properties.uid});
60
-
61
- return t.noInfoTag(properties.uid);
62
- });
63
-
64
- const classes = computed(() => {
65
- const result: {[key: string]: boolean} = {
66
- tag: true
67
- };
68
-
69
- if (tagsStates.value.requiredTags[properties.uid]) {
70
- result["required"] = true;
71
- }
72
-
73
- if (tagsStates.value.excludedTags[properties.uid]) {
74
- result["excluded"] = true;
75
- }
76
-
77
- if (properties.secondaryMode) {
78
- result[properties.secondaryMode] = true;
79
- }
80
-
81
- return result;
82
- });
83
-
84
- function onClick() {
85
- asserts.defined(tagsStates);
86
- tagsStates.value.onTagClicked({tag: properties.uid});
87
- }
88
-
89
- function onRevers() {
90
- asserts.defined(tagsStates);
91
- tagsStates.value.onTagReversed({tag: properties.uid});
92
- }
93
-
94
- const tooltip = computed(() => {
95
- if (properties.countMode == "tooltip" && properties.count) {
96
- return `articles with this tag: ${properties.count}`;
97
- }
98
- return "";
99
- });
100
- </script>
101
-
102
- <style scoped>
103
- .tag {
104
- @apply inline-block cursor-pointer p-0 mr-2 whitespace-nowrap hover:bg-green-100 px-1 hover:rounded-lg;
105
- }
106
-
107
- .tag.required {
108
- @apply text-green-700 font-bold;
109
- }
110
-
111
- .tag.excluded {
112
- @apply text-red-700 font-bold;
113
- }
114
-
115
- .tag.positive {
116
- @apply text-green-700;
117
- }
118
-
119
- .tag.negative {
120
- @apply text-red-700;
121
- }
122
- </style>