feeds-fun 1.18.2 → 1.19.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 +3 -1
- package/src/components/EntryForList.vue +6 -2
- package/src/components/FeedForList.vue +8 -5
- package/src/components/RuleForList.vue +3 -2
- package/src/components/collections/FeedItem.vue +10 -4
- package/src/components/main/Description.vue +7 -7
- package/src/components/main/Item.vue +1 -3
- package/src/components/main/NewsTitle.vue +1 -1
- package/src/components/page_footer/Footer.vue +106 -0
- package/src/components/page_header/ExternalLinks.vue +24 -46
- package/src/components/page_header/HomeButton.vue +14 -0
- package/src/components/side_pannel/CollapseButton.vue +7 -5
- package/src/components/tags/Base.vue +3 -1
- package/src/css/page_header.css +6 -2
- package/src/css/side_panel_layout.css +1 -1
- package/src/layouts/SidePanelLayout.vue +13 -9
- package/src/layouts/WideLayout.vue +2 -0
- package/src/logic/api.ts +10 -0
- package/src/logic/settings.ts +8 -0
- package/src/main.ts +14 -13
- package/src/plugins/CookieConsent.ts +170 -0
- package/src/router/index.ts +14 -0
- package/src/stores/globalState.ts +1 -1
- package/src/style.css +0 -2
- package/src/values/ExternalUrl.vue +4 -1
- package/src/values/Icon.vue +71 -0
- package/src/values/SocialLink.vue +81 -0
- package/src/views/AuthView.vue +22 -9
- package/src/views/CRMView.vue +41 -0
- package/src/views/MainView.vue +32 -17
- package/src/views/SettingsView.vue +33 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import "vanilla-cookieconsent/dist/cookieconsent.css";
|
|
2
|
+
import * as CookieConsent from "vanilla-cookieconsent";
|
|
3
|
+
|
|
4
|
+
import * as settings from "@/logic/settings";
|
|
5
|
+
|
|
6
|
+
export const plugin = {
|
|
7
|
+
install(app: any, pluginConfig: any): void {
|
|
8
|
+
app.config.globalProperties.$CookieConsent = CookieConsent;
|
|
9
|
+
CookieConsent.run(pluginConfig);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function showCookieConsent() {
|
|
14
|
+
CookieConsent.show(true);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isAnalyticsAllowed(): boolean {
|
|
18
|
+
return CookieConsent.acceptedCategory("analytics");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const plausibleId = "plausible-script";
|
|
22
|
+
|
|
23
|
+
function syncPlausible(): void {
|
|
24
|
+
if (!settings.plausibleEnabled) {
|
|
25
|
+
disablePlausible();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!isAnalyticsAllowed()) {
|
|
30
|
+
disablePlausible();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
enablePlausible();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isPlausibleEnabled() {
|
|
38
|
+
return document.getElementById(plausibleId) !== null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function disablePlausible() {
|
|
42
|
+
if (!isPlausibleEnabled()) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The simplest and straightforward way to disable smth is to reload the page
|
|
47
|
+
// We expect that users will not reevaluate the cookie consent modal often
|
|
48
|
+
window.location.reload();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function enablePlausible() {
|
|
52
|
+
if (isPlausibleEnabled()) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log("setup Plausible script");
|
|
57
|
+
|
|
58
|
+
const script = document.createElement("script");
|
|
59
|
+
script.id = plausibleId;
|
|
60
|
+
script.src = settings.plausibleScript;
|
|
61
|
+
script.async = true;
|
|
62
|
+
script.defer = true;
|
|
63
|
+
script.setAttribute("data-domain", settings.plausibleDomain);
|
|
64
|
+
document.body.appendChild(script);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const _description = `
|
|
68
|
+
<p>We use cookies and local storage for session tracking (required) and optional analytics.</p>
|
|
69
|
+
<p>Please let us collect analytics to better understand how you use us and become the best news reader ever.</p>
|
|
70
|
+
<p>You can find more information in our <a href="/privacy" target="_blank" rel="noopener noreferrer">privacy policy</a>.</p>
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
export const defaultConfig = {
|
|
74
|
+
revision: 1,
|
|
75
|
+
|
|
76
|
+
onConsent(): void {
|
|
77
|
+
syncPlausible();
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
onChange(): void {
|
|
81
|
+
syncPlausible();
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
categories: {
|
|
85
|
+
necessary: {
|
|
86
|
+
enabled: true,
|
|
87
|
+
readOnly: true
|
|
88
|
+
},
|
|
89
|
+
analytics: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
readOnly: false
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
language: {
|
|
96
|
+
default: "en",
|
|
97
|
+
translations: {
|
|
98
|
+
en: {
|
|
99
|
+
consentModal: {
|
|
100
|
+
title: "We use cookies and local storage",
|
|
101
|
+
description: _description,
|
|
102
|
+
acceptAllBtn: "Accept all",
|
|
103
|
+
acceptNecessaryBtn: "Reject all",
|
|
104
|
+
showPreferencesBtn: "Manage preferences"
|
|
105
|
+
},
|
|
106
|
+
preferencesModal: {
|
|
107
|
+
title: "Manage privacy preferences",
|
|
108
|
+
acceptAllBtn: "Accept all",
|
|
109
|
+
acceptNecessaryBtn: "Reject all",
|
|
110
|
+
savePreferencesBtn: "Accept current selection",
|
|
111
|
+
closeIconLabel: "Close",
|
|
112
|
+
sections: [
|
|
113
|
+
{
|
|
114
|
+
description: _description
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: "Strictly necessary data",
|
|
118
|
+
description: "This data is essential for the proper functioning of the website and cannot be disabled.",
|
|
119
|
+
|
|
120
|
+
linkedCategory: "necessary",
|
|
121
|
+
|
|
122
|
+
cookieTable: {
|
|
123
|
+
headers: {
|
|
124
|
+
name: "Data",
|
|
125
|
+
description: "Description"
|
|
126
|
+
},
|
|
127
|
+
body: [
|
|
128
|
+
{
|
|
129
|
+
name: "Session information",
|
|
130
|
+
description:
|
|
131
|
+
"We store your session information which is absolutely necessary for the website to work."
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: "Performance and analytics",
|
|
138
|
+
description: "These services collect information about how you use our website.",
|
|
139
|
+
linkedCategory: "analytics",
|
|
140
|
+
|
|
141
|
+
cookieTable: {
|
|
142
|
+
headers: {
|
|
143
|
+
name: "Service",
|
|
144
|
+
domain: "Domain",
|
|
145
|
+
description: "Description"
|
|
146
|
+
},
|
|
147
|
+
body: [
|
|
148
|
+
settings.plausibleEnabled
|
|
149
|
+
? {
|
|
150
|
+
name: "Plausible",
|
|
151
|
+
domain: "plausible.io",
|
|
152
|
+
description:
|
|
153
|
+
"EU-based, cookie-free service that helps us measure traffic and improve usability, without collecting personal data."
|
|
154
|
+
}
|
|
155
|
+
: null,
|
|
156
|
+
{
|
|
157
|
+
name: "Feeds Fun",
|
|
158
|
+
domain: "feeds.fun",
|
|
159
|
+
description:
|
|
160
|
+
"We collect our own analytics to improve the service. We do not share this data with third parties."
|
|
161
|
+
}
|
|
162
|
+
].filter((x) => x !== null)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
package/src/router/index.ts
CHANGED
|
@@ -8,7 +8,9 @@ import DiscoveryView from "../views/DiscoveryView.vue";
|
|
|
8
8
|
import CollectionsView from "../views/CollectionsView.vue";
|
|
9
9
|
import SettingsView from "../views/SettingsView.vue";
|
|
10
10
|
import PublicCollectionView from "../views/PublicCollectionView.vue";
|
|
11
|
+
import CRMView from "../views/CRMView.vue";
|
|
11
12
|
import * as e from "@/logic/enums";
|
|
13
|
+
import * as settings from "@/logic/settings";
|
|
12
14
|
|
|
13
15
|
// lazy view loading does not work with router.push function
|
|
14
16
|
// first attempt to router.push into not loaded view, will cause its loading, but will not change components
|
|
@@ -61,6 +63,18 @@ const router = createRouter({
|
|
|
61
63
|
name: "public-collection",
|
|
62
64
|
component: PublicCollectionView
|
|
63
65
|
},
|
|
66
|
+
{
|
|
67
|
+
path: "/terms",
|
|
68
|
+
name: "terms",
|
|
69
|
+
component: CRMView,
|
|
70
|
+
props: {content: settings.crmTerms, kind: "terms"}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
path: "/privacy",
|
|
74
|
+
name: "privacy",
|
|
75
|
+
component: CRMView,
|
|
76
|
+
props: {content: settings.crmPrivacy, kind: "privacy"}
|
|
77
|
+
},
|
|
64
78
|
{
|
|
65
79
|
path: "/:pathMatch(.*)*",
|
|
66
80
|
redirect: "/"
|
|
@@ -10,7 +10,7 @@ export const useGlobalState = defineStore("globalState", () => {
|
|
|
10
10
|
const supertokens = useSupertokens();
|
|
11
11
|
|
|
12
12
|
const isLoggedIn = computed(() => {
|
|
13
|
-
if (settings.
|
|
13
|
+
if (settings.isSingleUserMode) {
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
16
|
|
package/src/style.css
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="componentName"
|
|
4
|
+
class="inline-block"
|
|
5
|
+
v-bind="iconProperties" />
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts" setup>
|
|
9
|
+
import {computed} from "vue";
|
|
10
|
+
import {
|
|
11
|
+
IconArrowNarrowRight,
|
|
12
|
+
IconArrowRight,
|
|
13
|
+
IconPlus,
|
|
14
|
+
IconDots,
|
|
15
|
+
IconBrandReddit,
|
|
16
|
+
IconBrandDiscord,
|
|
17
|
+
IconBrandGithub,
|
|
18
|
+
IconExternalLink,
|
|
19
|
+
IconChevronsLeft,
|
|
20
|
+
IconChevronsRight,
|
|
21
|
+
IconLayoutSidebarLeftCollapse,
|
|
22
|
+
IconLayoutSidebarLeftExpand,
|
|
23
|
+
IconX,
|
|
24
|
+
IconMoodSmile,
|
|
25
|
+
IconMoodSad
|
|
26
|
+
} from "@tabler/icons-vue";
|
|
27
|
+
|
|
28
|
+
const iconMap = {
|
|
29
|
+
reddit: IconBrandReddit,
|
|
30
|
+
discord: IconBrandDiscord,
|
|
31
|
+
github: IconBrandGithub,
|
|
32
|
+
"arrow-narrow-right": IconArrowNarrowRight,
|
|
33
|
+
"arrow-right": IconArrowRight,
|
|
34
|
+
plus: IconPlus,
|
|
35
|
+
dots: IconDots,
|
|
36
|
+
"external-link": IconExternalLink,
|
|
37
|
+
"chevrons-right": IconChevronsRight,
|
|
38
|
+
"chevrons-left": IconChevronsLeft,
|
|
39
|
+
"sidebar-left-collapse": IconLayoutSidebarLeftCollapse,
|
|
40
|
+
"sidebar-left-expand": IconLayoutSidebarLeftExpand,
|
|
41
|
+
x: IconX,
|
|
42
|
+
"face-smile": IconMoodSmile,
|
|
43
|
+
"face-sad": IconMoodSad
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
type IconName = keyof typeof iconMap;
|
|
47
|
+
|
|
48
|
+
const sizeMap = {
|
|
49
|
+
small: 16,
|
|
50
|
+
medium: 20,
|
|
51
|
+
large: 24
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
type IconSize = keyof typeof sizeMap;
|
|
55
|
+
|
|
56
|
+
const properties = defineProps<{icon: IconName; size?: IconSize}>();
|
|
57
|
+
|
|
58
|
+
const componentName = computed(() => {
|
|
59
|
+
if (properties.icon in iconMap) {
|
|
60
|
+
return iconMap[properties.icon];
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error(`Icon ${properties.icon} not found`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const iconProperties = computed(() => {
|
|
67
|
+
return {
|
|
68
|
+
size: sizeMap[properties.size ?? "medium"]
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a
|
|
3
|
+
v-if="link.enabled"
|
|
4
|
+
:href="link.url"
|
|
5
|
+
target="_blank"
|
|
6
|
+
@click="events.socialLinkClicked({linkType: link.eventType, view: eventsView})">
|
|
7
|
+
<span v-if="mode === 'text'">
|
|
8
|
+
{{ link.text }}
|
|
9
|
+
|
|
10
|
+
<icon
|
|
11
|
+
icon="external-link"
|
|
12
|
+
size="small" />
|
|
13
|
+
</span>
|
|
14
|
+
|
|
15
|
+
<icon
|
|
16
|
+
v-else
|
|
17
|
+
:icon="link.icon" />
|
|
18
|
+
</a>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import {inject, computed} from "vue";
|
|
23
|
+
|
|
24
|
+
import * as events from "@/logic/events";
|
|
25
|
+
import * as settings from "@/logic/settings";
|
|
26
|
+
import * as asserts from "@/logic/asserts";
|
|
27
|
+
|
|
28
|
+
const links = {
|
|
29
|
+
api: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
url: "/api/docs",
|
|
32
|
+
text: "API",
|
|
33
|
+
icon: null,
|
|
34
|
+
eventType: "api"
|
|
35
|
+
},
|
|
36
|
+
blog: {
|
|
37
|
+
enabled: settings.blog !== null,
|
|
38
|
+
url: settings.blog,
|
|
39
|
+
text: "Blog",
|
|
40
|
+
icon: null,
|
|
41
|
+
eventType: "blog"
|
|
42
|
+
},
|
|
43
|
+
reddit: {
|
|
44
|
+
enabled: settings.redditSubreddit !== null,
|
|
45
|
+
url: settings.redditSubreddit,
|
|
46
|
+
text: "Reddit",
|
|
47
|
+
icon: "reddit",
|
|
48
|
+
eventType: "reddit"
|
|
49
|
+
},
|
|
50
|
+
discord: {
|
|
51
|
+
enabled: settings.discordInvite !== null,
|
|
52
|
+
url: settings.discordInvite,
|
|
53
|
+
text: "Discord",
|
|
54
|
+
icon: "discord",
|
|
55
|
+
eventType: "discord"
|
|
56
|
+
},
|
|
57
|
+
github: {
|
|
58
|
+
enabled: settings.githubRepo !== null,
|
|
59
|
+
url: settings.githubRepo,
|
|
60
|
+
text: "GitHub",
|
|
61
|
+
icon: "github",
|
|
62
|
+
eventType: "github"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type LinkKind = keyof typeof links;
|
|
67
|
+
|
|
68
|
+
const properties = defineProps<{kind: LinkKind; mode: "icon" | "text"}>();
|
|
69
|
+
|
|
70
|
+
const eventsView = inject<events.EventsViewName>("eventsViewName");
|
|
71
|
+
|
|
72
|
+
asserts.defined(eventsView);
|
|
73
|
+
|
|
74
|
+
const link = computed(() => {
|
|
75
|
+
if (properties.kind in links) {
|
|
76
|
+
return links[properties.kind];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error(`Link kind "${properties.kind}" not found`);
|
|
80
|
+
});
|
|
81
|
+
</script>
|
package/src/views/AuthView.vue
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<wide-layout>
|
|
3
|
-
<
|
|
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>
|
|
4
8
|
|
|
5
|
-
<
|
|
6
|
-
<p v-if="!linkProcessed">Checking login status...</p>
|
|
9
|
+
<hr />
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
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-block>
|
|
17
|
+
<div class="max-w-xl md:mx-auto ffun-info-good text-center mx-2">
|
|
18
|
+
<p>Checking login status...</p>
|
|
19
|
+
</div>
|
|
20
|
+
</main-block>
|
|
10
21
|
</wide-layout>
|
|
11
22
|
</template>
|
|
12
23
|
|
|
@@ -28,12 +39,14 @@
|
|
|
28
39
|
|
|
29
40
|
provide("eventsViewName", "auth");
|
|
30
41
|
|
|
31
|
-
const linkProcessed = ref(false);
|
|
32
|
-
|
|
33
42
|
function goToWorkspace() {
|
|
34
43
|
router.push({name: globalSettings.mainPanelMode, params: {}});
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
function goToMain() {
|
|
47
|
+
router.push({name: "main", params: {}});
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
onBeforeMount(async () => {
|
|
38
51
|
if (globalState.isLoggedIn) {
|
|
39
52
|
goToWorkspace();
|
|
@@ -46,6 +59,7 @@
|
|
|
46
59
|
|
|
47
60
|
async function onSignFailed() {
|
|
48
61
|
await supertokens.clearLoginAttempt();
|
|
62
|
+
goToMain();
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
if (await supertokens.hasInitialMagicLinkBeenSent()) {
|
|
@@ -54,9 +68,8 @@
|
|
|
54
68
|
onSignIn: onSignIn,
|
|
55
69
|
onSignFailed: onSignFailed
|
|
56
70
|
});
|
|
57
|
-
linkProcessed.value = true;
|
|
58
71
|
} else {
|
|
59
|
-
|
|
72
|
+
goToMain();
|
|
60
73
|
}
|
|
61
74
|
});
|
|
62
75
|
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<wide-layout>
|
|
3
|
+
<div class="ffun-page-header">
|
|
4
|
+
<div class="ffun-page-header-center-block">
|
|
5
|
+
<page-header-home-button />
|
|
6
|
+
<page-header-external-links :show-api="false" />
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<hr />
|
|
11
|
+
|
|
12
|
+
<main-block>
|
|
13
|
+
<h1 class="m-0 text-5xl">Feeds Fun</h1>
|
|
14
|
+
<p class="mt-2 text-2xl">Transparent Personalized News</p>
|
|
15
|
+
</main-block>
|
|
16
|
+
|
|
17
|
+
<hr />
|
|
18
|
+
|
|
19
|
+
<main-block>
|
|
20
|
+
<div
|
|
21
|
+
v-html="actualContent"
|
|
22
|
+
class="prose max-w-none" />
|
|
23
|
+
</main-block>
|
|
24
|
+
</wide-layout>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts" setup>
|
|
28
|
+
import {computed, provide} from "vue";
|
|
29
|
+
|
|
30
|
+
const properties = defineProps<{content: string | null; kind: string}>();
|
|
31
|
+
|
|
32
|
+
provide("eventsViewName", properties.kind);
|
|
33
|
+
|
|
34
|
+
const actualContent = computed(() => {
|
|
35
|
+
if (properties.content === null) {
|
|
36
|
+
return "You MUST define a content for this page in case you host public version of Feeds Fun.";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return properties.content;
|
|
40
|
+
});
|
|
41
|
+
</script>
|
package/src/views/MainView.vue
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<main-header-line> Smarter way to read news </main-header-line>
|
|
29
29
|
|
|
30
30
|
<main-block>
|
|
31
|
-
<main-description
|
|
31
|
+
<main-description step="1">
|
|
32
32
|
<template #caption> Subscribe to sites </template>
|
|
33
33
|
|
|
34
34
|
<template #description>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
</main-description>
|
|
38
38
|
|
|
39
|
-
<main-description
|
|
39
|
+
<main-description step="2">
|
|
40
40
|
<template #caption> Get automatic tagging </template>
|
|
41
41
|
|
|
42
42
|
<template #description>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
</template>
|
|
74
74
|
</main-description>
|
|
75
75
|
|
|
76
|
-
<main-description
|
|
76
|
+
<main-description step="3">
|
|
77
77
|
<template #caption> Create scoring rules </template>
|
|
78
78
|
|
|
79
79
|
<template #description>
|
|
@@ -85,9 +85,12 @@
|
|
|
85
85
|
:link="null"
|
|
86
86
|
css-modifier="positive" />
|
|
87
87
|
|
|
88
|
-
<
|
|
88
|
+
<icon
|
|
89
|
+
icon="arrow-right"
|
|
90
|
+
size="small"
|
|
91
|
+
class="mx-0.5" />
|
|
89
92
|
|
|
90
|
-
<span class="cursor-default text-purple-700 text-lg md:text-xl">+5</span>
|
|
93
|
+
<span class="inline-block align-middle cursor-default text-purple-700 text-lg md:text-xl">+5</span>
|
|
91
94
|
</div>
|
|
92
95
|
|
|
93
96
|
<div class="">
|
|
@@ -97,9 +100,12 @@
|
|
|
97
100
|
link="http://example.com"
|
|
98
101
|
css-modifier="negative" />
|
|
99
102
|
|
|
100
|
-
<
|
|
103
|
+
<icon
|
|
104
|
+
icon="arrow-right"
|
|
105
|
+
size="small"
|
|
106
|
+
class="mx-0.5" />
|
|
101
107
|
|
|
102
|
-
<span class="cursor-default text-purple-700 text-lg md:text-xl">-55</span>
|
|
108
|
+
<span class="inline-block align-middle cursor-default text-purple-700 text-lg md:text-xl">-55</span>
|
|
103
109
|
</div>
|
|
104
110
|
|
|
105
111
|
<div class="">
|
|
@@ -109,7 +115,10 @@
|
|
|
109
115
|
:link="null"
|
|
110
116
|
css-modifier="positive" />
|
|
111
117
|
|
|
112
|
-
<
|
|
118
|
+
<icon
|
|
119
|
+
icon="plus"
|
|
120
|
+
size="small"
|
|
121
|
+
class="mx-0.5" />
|
|
113
122
|
|
|
114
123
|
<fake-tag
|
|
115
124
|
uid="new-york"
|
|
@@ -117,9 +126,12 @@
|
|
|
117
126
|
:link="null"
|
|
118
127
|
css-modifier="positive" />
|
|
119
128
|
|
|
120
|
-
<
|
|
129
|
+
<icon
|
|
130
|
+
icon="arrow-right"
|
|
131
|
+
size="small"
|
|
132
|
+
class="mx-0.5" />
|
|
121
133
|
|
|
122
|
-
<span class="cursor-default-purple-700 text-lg md:text-xl">+8</span>
|
|
134
|
+
<span class="inline-block align-middle cursor-default text-purple-700 text-lg md:text-xl">+8</span>
|
|
123
135
|
</div>
|
|
124
136
|
|
|
125
137
|
<div class="">
|
|
@@ -129,15 +141,18 @@
|
|
|
129
141
|
:link="null"
|
|
130
142
|
css-modifier="positive" />
|
|
131
143
|
|
|
132
|
-
<
|
|
144
|
+
<icon
|
|
145
|
+
icon="arrow-right"
|
|
146
|
+
size="small"
|
|
147
|
+
class="mx-0.5" />
|
|
133
148
|
|
|
134
|
-
<span class="cursor-default text-purple-700 text-lg md:text-xl">+21</span>
|
|
149
|
+
<span class="inline-block align-middle cursor-default text-purple-700 text-lg md:text-xl">+21</span>
|
|
135
150
|
</div>
|
|
136
151
|
</div>
|
|
137
152
|
</template>
|
|
138
153
|
</main-description>
|
|
139
154
|
|
|
140
|
-
<main-description
|
|
155
|
+
<main-description step="4">
|
|
141
156
|
<template #caption> Read what matters </template>
|
|
142
157
|
|
|
143
158
|
<template #description>
|
|
@@ -151,7 +166,9 @@
|
|
|
151
166
|
title="Sci-fi novel about UFO in New Yourk"
|
|
152
167
|
:score="13" />
|
|
153
168
|
|
|
154
|
-
<
|
|
169
|
+
<div class="opacity-65 block justify-self-center">
|
|
170
|
+
<icon icon="dots" />
|
|
171
|
+
</div>
|
|
155
172
|
|
|
156
173
|
<main-news-title
|
|
157
174
|
class="opacity-55"
|
|
@@ -171,9 +188,7 @@
|
|
|
171
188
|
<template
|
|
172
189
|
v-for="collectionId in collections.collectionsOrder"
|
|
173
190
|
:key="collectionId">
|
|
174
|
-
<main-item
|
|
175
|
-
v-if="collections.collections[collectionId].showOnMain"
|
|
176
|
-
icon="ti-arrow-narrow-right">
|
|
191
|
+
<main-item v-if="collections.collections[collectionId].showOnMain">
|
|
177
192
|
<template #caption>
|
|
178
193
|
{{ collections.collections[collectionId].name }}
|
|
179
194
|
</template>
|
|
@@ -116,6 +116,32 @@
|
|
|
116
116
|
</tr>
|
|
117
117
|
</tbody>
|
|
118
118
|
</table>
|
|
119
|
+
|
|
120
|
+
<h3>Danger Zone</h3>
|
|
121
|
+
|
|
122
|
+
<div class="ffun-info-bad">
|
|
123
|
+
<p><strong>ATTENTION!</strong></p>
|
|
124
|
+
|
|
125
|
+
<p> Operations in this section are irreversible and may lead to data loss and even account deletion. </p>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div
|
|
129
|
+
v-if="!settings.isSingleUserMode"
|
|
130
|
+
class="ffun-info-bad">
|
|
131
|
+
<button
|
|
132
|
+
@click.prevent="removeAccount()"
|
|
133
|
+
class="ffun-form-button bad short ml-1"
|
|
134
|
+
>Remove Account</button
|
|
135
|
+
>
|
|
136
|
+
|
|
137
|
+
<label class="ml-1"> Permanently remove your account and all your data. </label>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div
|
|
141
|
+
v-else
|
|
142
|
+
class="ffun-info-common">
|
|
143
|
+
<p> Account removal in the single-user mode is not available. </p>
|
|
144
|
+
</div>
|
|
119
145
|
</side-panel-layout>
|
|
120
146
|
</template>
|
|
121
147
|
|
|
@@ -125,6 +151,7 @@
|
|
|
125
151
|
import * as api from "@/logic/api";
|
|
126
152
|
import * as t from "@/logic/types";
|
|
127
153
|
import * as e from "@/logic/enums";
|
|
154
|
+
import * as settings from "@/logic/settings";
|
|
128
155
|
import {useRouter} from "vue-router";
|
|
129
156
|
import {useGlobalSettingsStore} from "@/stores/globalSettings";
|
|
130
157
|
|
|
@@ -158,6 +185,12 @@
|
|
|
158
185
|
router.push({name: e.MainPanelMode.Collections, params: {}});
|
|
159
186
|
}
|
|
160
187
|
|
|
188
|
+
function removeAccount() {
|
|
189
|
+
if (confirm("Are you sure you want to remove your account? THIS OPERATION IS NOT REVERSIBLE!")) {
|
|
190
|
+
api.removeUser();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
161
194
|
// TODO: check api keys on setup
|
|
162
195
|
// TODO: basic integer checks
|
|
163
196
|
</script>
|