feeds-fun 0.5.1 → 1.1.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 (45) hide show
  1. package/package.json +25 -23
  2. package/postcss.config.js +6 -0
  3. package/src/components/ConfigFlag.vue +6 -14
  4. package/src/components/ConfigSelector.vue +3 -1
  5. package/src/components/DiscoveryForm.vue +9 -2
  6. package/src/components/EntriesList.vue +15 -4
  7. package/src/components/EntryForList.vue +76 -65
  8. package/src/components/FeedForList.vue +15 -32
  9. package/src/components/FeedInfo.vue +11 -4
  10. package/src/components/FeedsCollections.vue +23 -15
  11. package/src/components/FeedsList.vue +7 -18
  12. package/src/components/FfunTag.vue +6 -13
  13. package/src/components/NotificationCollections.vue +7 -0
  14. package/src/components/NotificationOpenaiApiKey.vue +28 -0
  15. package/src/components/Notifications.vue +32 -0
  16. package/src/components/OPMLUpload.vue +3 -0
  17. package/src/components/RuleConstructor.vue +19 -27
  18. package/src/components/RuleForList.vue +47 -0
  19. package/src/components/RulesList.vue +7 -43
  20. package/src/components/SimplePagination.vue +4 -6
  21. package/src/components/SupertokensLogin.vue +50 -25
  22. package/src/components/TagsFilter.vue +13 -25
  23. package/src/components/TagsList.vue +27 -25
  24. package/src/components/UserSetting.vue +41 -38
  25. package/src/components/UserSettingForNotification.vue +63 -0
  26. package/src/inputs/Marker.vue +3 -7
  27. package/src/inputs/ScoreSelector.vue +11 -12
  28. package/src/layouts/SidePanelLayout.vue +42 -96
  29. package/src/layouts/WideLayout.vue +2 -25
  30. package/src/logic/api.ts +3 -3
  31. package/src/logic/settings.ts +0 -9
  32. package/src/main.ts +12 -19
  33. package/src/stores/entries.ts +1 -6
  34. package/src/style.css +92 -0
  35. package/src/values/Score.vue +1 -10
  36. package/src/views/AuthView.vue +3 -7
  37. package/src/views/CollectionsView.vue +2 -0
  38. package/src/views/FeedsView.vue +8 -3
  39. package/src/views/MainView.vue +36 -83
  40. package/src/views/NewsView.vue +30 -13
  41. package/src/views/RulesView.vue +0 -2
  42. package/src/views/SettingsView.vue +35 -22
  43. package/tailwind.config.js +14 -0
  44. package/src/components/EntryInfo.vue +0 -23
  45. package/src/components/FfunGithubButtons.vue +0 -22
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div class="ffun-info-attention">
3
+ <p>
4
+ Because, for now, our service is free to use and OpenAI API costs money, we politely ask you to set up your own
5
+ OpenAI API key.
6
+ </p>
7
+ <p>
8
+ You can do this on the
9
+ <a
10
+ href="#"
11
+ @click.prevent="router.push({name: e.MainPanelMode.Settings, params: {}})"
12
+ >settings</a
13
+ >
14
+ page.
15
+ </p>
16
+
17
+ <user-setting-for-notification
18
+ kind="openai_hide_message_about_setting_up_key"
19
+ button-text="Hide this message" />
20
+ </div>
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import {useRouter} from "vue-router";
25
+ import * as e from "@/logic/enums";
26
+
27
+ const router = useRouter();
28
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <notification-openai-api-key v-if="showOpenAIKeyNotification" />
3
+ <notification-collections v-if="showCollectionsNotification" />
4
+ </template>
5
+
6
+ <script lang="ts" setup>
7
+ import {computed, ref, onUnmounted, watch} from "vue";
8
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
9
+
10
+ const properties = defineProps<{
11
+ openaiApiKey: boolean;
12
+ collections: boolean;
13
+ }>();
14
+
15
+ const globalSettings = useGlobalSettingsStore();
16
+
17
+ const showApiKeyMessage = computed(() => {
18
+ return (
19
+ globalSettings.userSettings &&
20
+ !globalSettings.userSettings.openai_api_key.value &&
21
+ !globalSettings.userSettings.openai_hide_message_about_setting_up_key.value
22
+ );
23
+ });
24
+
25
+ const showCollectionsNotification = computed(() => {
26
+ return properties.collections;
27
+ });
28
+
29
+ const showOpenAIKeyNotification = computed(() => {
30
+ return !showCollectionsNotification.value && properties.openaiApiKey && showApiKeyMessage.value;
31
+ });
32
+ </script>
@@ -2,8 +2,11 @@
2
2
  <div>
3
3
  <input
4
4
  type="file"
5
+ class="ffun-file-button"
5
6
  @change="uploadFile" />
7
+
6
8
  <button
9
+ class="ffun-form-button"
7
10
  type="submit"
8
11
  @click.prevent="submit"
9
12
  >Submit</button
@@ -1,25 +1,14 @@
1
1
  <template>
2
- <div class="rule-constructor">
3
- <p>Select tags to score news by them.</p>
4
-
5
- <template
6
- v-for="tag of tags"
7
- :key="tag">
8
- <ffun-tag
9
- :uid="tag"
10
- mode="required" />&nbsp;
11
- </template>
12
-
13
- <br />
14
-
15
- <score-selector v-model="currentScore" />
16
-
17
- &nbsp;
2
+ <div class="">
3
+ <score-selector
4
+ class="inline-block mr-2"
5
+ v-model="currentScore" />
18
6
 
19
7
  <a
8
+ class="ffun-form-button"
20
9
  href="#"
21
10
  v-if="canCreateRule"
22
- @click.prevent="createRule()"
11
+ @click.prevent="createOrUpdateRule()"
23
12
  >create rule</a
24
13
  >
25
14
  </div>
@@ -27,9 +16,12 @@
27
16
 
28
17
  <script lang="ts" setup>
29
18
  import {computed, ref} from "vue";
19
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
30
20
  import * as api from "@/logic/api";
31
21
  const properties = defineProps<{tags: string[]}>();
32
22
 
23
+ const globalSettings = useGlobalSettingsStore();
24
+
33
25
  const emit = defineEmits(["rule-constructor:created"]);
34
26
 
35
27
  // fibonacci numbers
@@ -41,16 +33,16 @@
41
33
  return properties.tags.length > 0;
42
34
  });
43
35
 
44
- async function createRule() {
45
- await api.createRule({tags: properties.tags, score: currentScore.value});
36
+ async function createOrUpdateRule() {
37
+ await api.createOrUpdateRule({tags: properties.tags, score: currentScore.value});
38
+
39
+ // this line leads to the reloading of news and any other data
40
+ // not an elegant solution, but it works with the current API implementation
41
+ // TODO: try to refactor to only update scores of news:
42
+ // - without reloading
43
+ // - maybe, without reordering too
44
+ globalSettings.updateDataVersion();
45
+
46
46
  emit("rule-constructor:created");
47
47
  }
48
48
  </script>
49
-
50
- <style scoped>
51
- .rule-constructor {
52
- padding: 0.25rem;
53
- margin: 0.25rem;
54
- border: 1px solid #ccc;
55
- }
56
- </style>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <div
3
+ v-if="rule !== null"
4
+ class="flex mb-1">
5
+ <div class="flex-shrink-0 min-w-fit mr-2">
6
+ <a
7
+ href="#"
8
+ class="ffun-normal-link"
9
+ @click.prevent="deleteRule()">
10
+ remove
11
+ </a>
12
+ </div>
13
+
14
+ <rule-score-updater
15
+ class="flex-shrink-0 mr-2"
16
+ :score="rule.score"
17
+ :rule-id="rule.id"
18
+ :tags="rule.tags" />
19
+
20
+ <div class="flex-grow">
21
+ <template
22
+ v-for="tag of rule.tags"
23
+ :key="tag">
24
+ <ffun-tag :uid="tag" />&nbsp;
25
+ </template>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script lang="ts" setup>
31
+ import {computed, ref} from "vue";
32
+ import type * as t from "@/logic/types";
33
+ import * as e from "@/logic/enums";
34
+ import * as api from "@/logic/api";
35
+ import {computedAsync} from "@vueuse/core";
36
+ import DOMPurify from "dompurify";
37
+ import {useGlobalSettingsStore} from "@/stores/globalSettings";
38
+
39
+ const globalSettings = useGlobalSettingsStore();
40
+
41
+ const properties = defineProps<{rule: t.Rule}>();
42
+
43
+ async function deleteRule() {
44
+ await api.deleteRule({id: properties.rule.id});
45
+ globalSettings.updateDataVersion();
46
+ }
47
+ </script>
@@ -1,38 +1,11 @@
1
1
  <template>
2
- <table>
3
- <thead>
4
- <tr>
5
- <th>score</th>
6
- <th>tags</th>
7
- </tr>
8
- </thead>
9
- <tbody>
10
- <tr
11
- v-for="rule in rules"
12
- :key="rule.id">
13
- <td>
14
- <rule-score-updater
15
- :score="rule.score"
16
- :rule-id="rule.id"
17
- :tags="rule.tags" />
18
- </td>
19
- <td>
20
- <template
21
- v-for="tag of rule.tags"
22
- :key="tag">
23
- <ffun-tag :uid="tag" />&nbsp;
24
- </template>
25
- </td>
26
- <td>
27
- <a
28
- href="#"
29
- @click.prevent="deleteRule(rule.id)"
30
- >delete</a
31
- >
32
- </td>
33
- </tr>
34
- </tbody>
35
- </table>
2
+ <ul>
3
+ <li
4
+ v-for="rule in rules"
5
+ :key="rule.id">
6
+ <rule-for-list :rule="rule" />
7
+ </li>
8
+ </ul>
36
9
  </template>
37
10
 
38
11
  <script lang="ts" setup>
@@ -42,13 +15,4 @@
42
15
  import {useGlobalSettingsStore} from "@/stores/globalSettings";
43
16
 
44
17
  defineProps<{rules: Array<t.Rule>}>();
45
-
46
- const globalSettings = useGlobalSettingsStore();
47
-
48
- async function deleteRule(id: t.RuleId) {
49
- await api.deleteRule({id: id});
50
- globalSettings.updateDataVersion();
51
- }
52
18
  </script>
53
-
54
- <style></style>
@@ -1,14 +1,16 @@
1
1
  <template>
2
2
  <div>
3
+ {{ realShowEntries }} of {{ total }}
4
+
3
5
  <button
6
+ class="ffun-form-button ml-2"
4
7
  v-if="canShowMore"
5
8
  @click.prevent="showMore()">
6
9
  next {{ realShowPerPage }}
7
10
  </button>
8
11
 
9
- <span v-if="canShowMore && canHide"> | </span>
10
-
11
12
  <button
13
+ class="ffun-form-button ml-2"
12
14
  v-if="canHide"
13
15
  @click.prevent="hideAll()"
14
16
  >hide</button
@@ -19,8 +21,6 @@
19
21
  style="line-height: 0.5rem"
20
22
  >&nbsp;</div
21
23
  >
22
-
23
- shown {{ realShowEntries }} / {{ total }}
24
24
  </div>
25
25
  </template>
26
26
 
@@ -77,5 +77,3 @@
77
77
  return showEntries.value < properties.total;
78
78
  });
79
79
  </script>
80
-
81
- <style></style>
@@ -1,46 +1,62 @@
1
1
  <template>
2
- <div>
2
+ <div v-if="globalState.isLoggedIn">
3
+ <p class="">You have already logged in.</p>
4
+ <button
5
+ class="text-blue-600 hover:text-blue-800 text-xl pt-0"
6
+ @click.prevent="goToWorkspace()"
7
+ >Go To Feeds ⇒</button
8
+ >
9
+ </div>
10
+
11
+ <div v-else>
3
12
  <template v-if="!requested">
4
- <p>Will send you an email with a login link.</p>
13
+ <p class="text-left">We'll send you an email with a login link.</p>
5
14
 
6
- <p>If you don&apos;t have an account, one will be created.</p>
15
+ <p class="text-left">If you don&apos;t have an account, one will be created.</p>
7
16
 
8
- <form @submit.prevent="login()">
17
+ <form
18
+ @submit.prevent="login()"
19
+ class="w-full flex">
9
20
  <input
21
+ class="ffun-input flex-grow p-1 mr-2"
10
22
  type="email"
11
23
  v-model="email"
12
24
  required
13
- placeholder="your-account-email@example.com" />
14
- <button type="submit">Login</button>
25
+ placeholder="me@example.com" />
26
+ <button
27
+ class="ffun-form-button"
28
+ type="submit"
29
+ >Login</button
30
+ >
15
31
  </form>
16
32
  </template>
17
33
 
18
34
  <template v-else>
19
- <p>
20
- Email with login link has been sent to <strong>{{ email }}</strong>
35
+ <p class="">
36
+ Login link was sent to <strong>{{ email }}</strong>
37
+
38
+ <a
39
+ class="ml-1"
40
+ @click.prevent="onChangeEmail()"
41
+ >change</a
42
+ >
21
43
  </p>
22
44
 
23
45
  <button
46
+ v-if="!counting"
24
47
  type="button"
25
- class="btn btn-primary"
26
- :disabled="counting">
27
- <vue-countdown
28
- v-if="counting"
29
- :time="supertokens.allowResendAfter"
30
- @end="onCountdownEnd"
31
- v-slot="{totalSeconds}">
32
- Resend the email in {{ totalSeconds }} seconds.
33
- </vue-countdown>
34
- <span
35
- v-else
36
- @click.prevent="resend()"
37
- >Resend email</span
38
- >
48
+ class="btn btn-primary ffun-form-button">
49
+ <span @click.prevent="resend()">Resend email</span>
39
50
  </button>
40
51
 
41
- <br /><br />
42
-
43
- <button @click.prevent="onChangeEmail()">Change email</button>
52
+ <vue-countdown
53
+ v-else
54
+ :time="supertokens.allowResendAfter"
55
+ @end="onCountdownEnd"
56
+ class=""
57
+ v-slot="{totalSeconds}">
58
+ Resend in {{ totalSeconds }} seconds
59
+ </vue-countdown>
44
60
  </template>
45
61
  </div>
46
62
  </template>
@@ -54,7 +70,12 @@
54
70
  import DOMPurify from "dompurify";
55
71
  import {useEntriesStore} from "@/stores/entries";
56
72
  import {useSupertokens} from "@/stores/supertokens";
73
+ import {useGlobalState} from "@/stores/globalState";
74
+ import {useRouter} from "vue-router";
57
75
  import * as settings from "@/logic/settings";
76
+ const router = useRouter();
77
+
78
+ const globalState = useGlobalState();
58
79
 
59
80
  const supertokens = useSupertokens();
60
81
 
@@ -106,6 +127,10 @@
106
127
  await supertokens.clearLoginAttempt();
107
128
  }
108
129
 
130
+ function goToWorkspace() {
131
+ router.push({name: e.MainPanelMode.Entries, params: {}});
132
+ }
133
+
109
134
  onMounted(async () => {
110
135
  if (settings.authMode === settings.AuthMode.SingleUser) {
111
136
  return true;
@@ -2,40 +2,40 @@
2
2
  <div>
3
3
  <ul
4
4
  v-if="displayedSelectedTags.length > 0"
5
- style="list-style: none; padding: 0; margin: 0">
5
+ class="pl-0 mb-0">
6
6
  <li
7
- class="filter-element"
8
7
  v-for="tag of displayedSelectedTags"
9
- :key="tag">
8
+ :key="tag"
9
+ class="whitespace-nowrap line-clamp-1">
10
+ <a
11
+ href="#"
12
+ @click.prevent="deselect(tag)"
13
+ >[X]</a
14
+ >
10
15
  <ffun-tag
16
+ class="ml-1"
11
17
  :uid="tag"
12
18
  :count="tags[tag] ?? 0"
13
19
  count-mode="no"
14
20
  :mode="tagStates[tag]"
15
21
  @tag:clicked="onTagClicked">
16
- <template #start>
17
- <a
18
- href="#"
19
- @click.prevent="deselect(tag)"
20
- >[X]</a
21
- >
22
- </template>
23
22
  </ffun-tag>
24
23
  </li>
25
24
  </ul>
26
25
 
27
26
  <input
27
+ class="ffun-input w-full"
28
28
  type="text"
29
29
  placeholder="Input part of tag..."
30
30
  v-model="tagNameFilter" />
31
31
 
32
32
  <ul
33
33
  v-if="displayedTags.length > 0"
34
- style="list-style: none; padding: 0; margin: 0">
34
+ class="pl-0 mb-0">
35
35
  <li
36
- class="filter-element"
37
36
  v-for="tag of displayedTags"
38
- :key="tag">
37
+ :key="tag"
38
+ class="truncate">
39
39
  <ffun-tag
40
40
  :uid="tag"
41
41
  :count="tags[tag]"
@@ -172,15 +172,3 @@
172
172
  emit("tag:stateChanged", {tag: tag, state: tagStates.value[tag]});
173
173
  }
174
174
  </script>
175
-
176
- <style scoped>
177
- .filter-element {
178
- overflow: hidden;
179
- white-space: nowrap;
180
- }
181
-
182
- .filter-element value-tag {
183
- overflow: hidden;
184
- text-overflow: ellipsis;
185
- }
186
- </style>
@@ -1,32 +1,36 @@
1
1
  <template>
2
2
  <div>
3
+ <div class="text-sm">
4
+ <ffun-tag
5
+ v-for="tag of displayedTags"
6
+ :key="tag"
7
+ :uid="tag"
8
+ :mode="tagMode(tag)"
9
+ :count="tagsCount[tag]"
10
+ count-mode="tooltip"
11
+ @tag:clicked="onTagClicked" />
12
+
13
+ <a
14
+ class="ffun-normal-link"
15
+ href="#"
16
+ v-if="canShowAll"
17
+ @click.prevent="showAll = true"
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
27
+ >
28
+ </div>
29
+
3
30
  <rule-constructor
4
31
  v-if="selectedTagsList.length > 0"
5
32
  :tags="selectedTagsList"
6
33
  @rule-constructor:created="onRuleCreated" />
7
-
8
- <ffun-tag
9
- v-for="tag of displayedTags"
10
- :key="tag"
11
- :uid="tag"
12
- :mode="tagMode(tag)"
13
- :count="tagsCount[tag]"
14
- count-mode="tooltip"
15
- @tag:clicked="onTagClicked" />
16
-
17
- <a
18
- href="#"
19
- v-if="canShowAll"
20
- @click.prevent="showAll = true"
21
- >{{ tagsNumber - showLimit }} more</a
22
- >
23
-
24
- <a
25
- href="#"
26
- v-if="canHide"
27
- @click.prevent="showAll = false"
28
- >hide</a
29
- >
30
34
  </div>
31
35
  </template>
32
36
 
@@ -159,5 +163,3 @@
159
163
  selectedTags.value = {};
160
164
  }
161
165
  </script>
162
-
163
- <style></style>
@@ -1,44 +1,52 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="setting !== null"
4
- style="margin-bottom: 1rem">
5
- <label>
6
- <strong>{{ setting.name }}:</strong>
7
- &nbsp;
8
- <input
9
- v-if="editing"
10
- type="input"
11
- v-model="value" />
12
- <span v-else>{{ verboseValue }}</span>
13
- </label>
14
-
15
- &nbsp;
16
-
17
- <template v-if="setting.type == 'boolean'">
18
- <button
19
- v-if="!setting.value"
20
- @click.prevent="turnOn()"
21
- >Turn on</button
22
- >
4
+ class="mb-4">
5
+ <span class="font-semibold mr-1">{{ setting.name }}</span>
6
+
7
+ <input
8
+ class="ffun-input"
9
+ v-if="editing"
10
+ type="input"
11
+ v-model="value" />
12
+
13
+ <input
14
+ v-else-if="setting.type !== 'boolean'"
15
+ class="ffun-input"
16
+ disabled
17
+ :value="verboseValue" />
18
+
19
+ <config-flag
20
+ v-if="setting.type == 'boolean'"
21
+ style="min-width: 2.5rem"
22
+ :flag="setting.value"
23
+ @update:flag="updateFlag($event)"
24
+ on-text="No"
25
+ off-text="Yes" />
23
26
 
27
+ <template v-else-if="!editing">
24
28
  <button
25
- v-if="setting.value"
26
- @click.prevent="turnOff()"
27
- >Turn off</button
29
+ class="ffun-form-button ml-1"
30
+ @click.prevent="startEditing()"
31
+ >Edit</button
28
32
  >
29
33
  </template>
30
34
 
31
- <template v-else-if="!editing">
32
- <button @click.prevent="startEditing()">Edit</button>
33
- </template>
34
-
35
35
  <template v-else>
36
- <button @click.prevent="save()">Save</button>
37
- &nbsp;
38
- <button @click.prevent="cancel()">Cancel</button>
36
+ <button
37
+ @click.prevent="save()"
38
+ class="ffun-form-button ml-1"
39
+ >Save</button
40
+ >
41
+ <button
42
+ @click.prevent="cancel()"
43
+ class="ffun-form-button ml-1"
44
+ >Cancel</button
45
+ >
39
46
  </template>
40
47
 
41
48
  <div
49
+ class="ffun-normalized-text"
42
50
  v-if="setting.description"
43
51
  v-html="setting.description" />
44
52
  </div>
@@ -74,7 +82,7 @@
74
82
 
75
83
  const verboseValue = computed(() => {
76
84
  if (setting.value === null) {
77
- return "";
85
+ return "no value";
78
86
  }
79
87
 
80
88
  const v = setting.value.value;
@@ -85,7 +93,7 @@
85
93
  }
86
94
 
87
95
  if (v == null || v == "") {
88
- return "";
96
+ return "no value";
89
97
  }
90
98
 
91
99
  if (type == "secret") {
@@ -115,13 +123,8 @@
115
123
  editing.value = true;
116
124
  }
117
125
 
118
- async function turnOn() {
119
- value.value = true;
120
- await save();
121
- }
122
-
123
- async function turnOff() {
124
- value.value = false;
126
+ async function updateFlag(newValue: any) {
127
+ value.value = newValue;
125
128
  await save();
126
129
  }
127
130
  </script>