Skip to content

Commit b9e3bb9

Browse files
authored
feat(frontend): add Atom feed settings page and notifications discovery (#2760)
1 parent 6b14307 commit b9e3bb9

6 files changed

Lines changed: 120 additions & 2 deletions

File tree

frontend/src/components/misc/Icon.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
faPlay,
5959
faPlus,
6060
faPowerOff,
61+
faRss,
6162
faSearch,
6263
faShareAlt,
6364
faSignOutAlt,
@@ -168,6 +169,7 @@ library.add(faPercent)
168169
library.add(faPlay)
169170
library.add(faPlus)
170171
library.add(faPowerOff)
172+
library.add(faRss)
171173
library.add(faSave)
172174
library.add(faSearch)
173175
library.add(faShareAlt)

frontend/src/components/notifications/Notifications.vue

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,18 @@
2424
ref="popup"
2525
class="notifications-list"
2626
>
27-
<span class="head">{{ $t('notification.title') }}</span>
27+
<div class="head">
28+
<span>{{ $t('notification.title') }}</span>
29+
<BaseButton
30+
v-tooltip="$t('notification.subscribeFeed')"
31+
class="feed-link"
32+
:to="{name: 'user.settings.feeds'}"
33+
@click="showNotifications = false"
34+
>
35+
<span class="is-sr-only">{{ $t('notification.subscribeFeed') }}</span>
36+
<Icon icon="rss" />
37+
</BaseButton>
38+
</div>
2839
<div
2940
v-for="(n, index) in notifications"
3041
:key="n.id"
@@ -284,6 +295,19 @@ async function markAllRead() {
284295
font-family: $vikunja-font;
285296
font-size: 1rem;
286297
padding: .5rem;
298+
display: flex;
299+
align-items: center;
300+
justify-content: space-between;
301+
302+
.feed-link {
303+
color: var(--grey-500);
304+
transition: color $transition;
305+
306+
&:hover,
307+
&:focus {
308+
color: var(--primary);
309+
}
310+
}
287311
}
288312
289313
.single-notification {

frontend/src/i18n/lang/en.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,13 @@
219219
"usernameIs": "Your username for CalDAV is: {0}",
220220
"apiTokenHint": "You can also use an API token with CalDAV permission. Create one in {link}."
221221
},
222+
"feeds": {
223+
"title": "Atom Feed",
224+
"howTo": "You can subscribe to your Vikunja notifications from any Atom-compatible feed reader. Use the following URL:",
225+
"usernameIs": "Your username for the feed is: {0}",
226+
"apiTokenHint": "Authenticate with an API token that has the {scope} permission. Create one in {link}.",
227+
"tokenTitle": "Atom feed"
228+
},
222229
"avatar": {
223230
"title": "Avatar",
224231
"initials": "Initials",
@@ -1323,7 +1330,8 @@
13231330
"none": "You don't have any notifications. Have a nice day!",
13241331
"explainer": "Notifications will appear here when actions, projects or tasks you subscribed to happen.",
13251332
"markAllRead": "Mark all notifications as read",
1326-
"markAllReadSuccess": "Successfully marked all notifications as read."
1333+
"markAllReadSuccess": "Successfully marked all notifications as read.",
1334+
"subscribeFeed": "Subscribe to notifications via Atom feed"
13271335
},
13281336
"quickActions": {
13291337
"notLoggedIn": "Please log in to the main Vikunja window first.",

frontend/src/router/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ const router = createRouter({
117117
name: 'user.settings.data-export',
118118
component: () => import('@/views/user/settings/DataExport.vue'),
119119
},
120+
{
121+
path: '/user/settings/feeds',
122+
name: 'user.settings.feeds',
123+
component: () => import('@/views/user/settings/AtomFeed.vue'),
124+
},
120125
{
121126
path: '/user/settings/deletion',
122127
name: 'user.settings.deletion',

frontend/src/views/user/Settings.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ const navigationItems = computed(() => {
6767
routeName: 'user.settings.caldav',
6868
condition: caldavEnabled.value,
6969
},
70+
{
71+
title: t('user.settings.feeds.title'),
72+
routeName: 'user.settings.feeds',
73+
},
7074
{
7175
title: t('user.settings.apiTokens.title'),
7276
routeName: 'user.settings.apiTokens',
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<template>
2+
<Card :title="$t('user.settings.feeds.title')">
3+
<p>
4+
{{ $t('user.settings.feeds.howTo') }}
5+
</p>
6+
<FormField
7+
v-model="feedUrl"
8+
type="text"
9+
readonly
10+
>
11+
<template #addon>
12+
<XButton
13+
v-tooltip="$t('misc.copy')"
14+
:shadow="false"
15+
icon="paste"
16+
@click="copy(feedUrl)"
17+
/>
18+
</template>
19+
</FormField>
20+
21+
<p class="mbs-4">
22+
<i18n-t
23+
keypath="user.settings.feeds.usernameIs"
24+
scope="global"
25+
>
26+
<strong>{{ username }}</strong>
27+
</i18n-t>
28+
</p>
29+
30+
<p class="mbs-2">
31+
<i18n-t
32+
keypath="user.settings.feeds.apiTokenHint"
33+
scope="global"
34+
>
35+
<template #scope>
36+
<code>feeds:access</code>
37+
</template>
38+
<template #link>
39+
<RouterLink
40+
:to="{
41+
name: 'user.settings.apiTokens',
42+
query: {
43+
title: $t('user.settings.feeds.tokenTitle'),
44+
scopes: 'feeds:access',
45+
},
46+
}"
47+
>
48+
{{ $t('user.settings.apiTokens.title') }}
49+
</RouterLink>
50+
</template>
51+
</i18n-t>
52+
</p>
53+
</Card>
54+
</template>
55+
56+
<script lang="ts" setup>
57+
import {computed} from 'vue'
58+
import {useI18n} from 'vue-i18n'
59+
60+
import {useTitle} from '@/composables/useTitle'
61+
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
62+
import FormField from '@/components/input/FormField.vue'
63+
import {useConfigStore} from '@/stores/config'
64+
import {useAuthStore} from '@/stores/auth'
65+
66+
const copy = useCopyToClipboard()
67+
68+
const {t} = useI18n({useScope: 'global'})
69+
useTitle(() => `${t('user.settings.feeds.title')} - ${t('user.settings.title')}`)
70+
71+
const authStore = useAuthStore()
72+
const configStore = useConfigStore()
73+
const username = computed(() => authStore.info?.username)
74+
const feedUrl = computed(() => `${configStore.apiBase}/feeds/notifications.atom`)
75+
</script>

0 commit comments

Comments
 (0)