feat(frontend): add Atom feed settings page and notifications discovery (#2760)

This commit is contained in:
kolaente 2026-05-15 19:28:29 +02:00 committed by GitHub
parent 6b14307896
commit b9e3bb95fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 120 additions and 2 deletions

View File

@ -58,6 +58,7 @@ import {
faPlay,
faPlus,
faPowerOff,
faRss,
faSearch,
faShareAlt,
faSignOutAlt,
@ -168,6 +169,7 @@ library.add(faPercent)
library.add(faPlay)
library.add(faPlus)
library.add(faPowerOff)
library.add(faRss)
library.add(faSave)
library.add(faSearch)
library.add(faShareAlt)

View File

@ -24,7 +24,18 @@
ref="popup"
class="notifications-list"
>
<span class="head">{{ $t('notification.title') }}</span>
<div class="head">
<span>{{ $t('notification.title') }}</span>
<BaseButton
v-tooltip="$t('notification.subscribeFeed')"
class="feed-link"
:to="{name: 'user.settings.feeds'}"
@click="showNotifications = false"
>
<span class="is-sr-only">{{ $t('notification.subscribeFeed') }}</span>
<Icon icon="rss" />
</BaseButton>
</div>
<div
v-for="(n, index) in notifications"
:key="n.id"
@ -284,6 +295,19 @@ async function markAllRead() {
font-family: $vikunja-font;
font-size: 1rem;
padding: .5rem;
display: flex;
align-items: center;
justify-content: space-between;
.feed-link {
color: var(--grey-500);
transition: color $transition;
&:hover,
&:focus {
color: var(--primary);
}
}
}
.single-notification {

View File

@ -219,6 +219,13 @@
"usernameIs": "Your username for CalDAV is: {0}",
"apiTokenHint": "You can also use an API token with CalDAV permission. Create one in {link}."
},
"feeds": {
"title": "Atom Feed",
"howTo": "You can subscribe to your Vikunja notifications from any Atom-compatible feed reader. Use the following URL:",
"usernameIs": "Your username for the feed is: {0}",
"apiTokenHint": "Authenticate with an API token that has the {scope} permission. Create one in {link}.",
"tokenTitle": "Atom feed"
},
"avatar": {
"title": "Avatar",
"initials": "Initials",
@ -1323,7 +1330,8 @@
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions, projects or tasks you subscribed to happen.",
"markAllRead": "Mark all notifications as read",
"markAllReadSuccess": "Successfully marked all notifications as read."
"markAllReadSuccess": "Successfully marked all notifications as read.",
"subscribeFeed": "Subscribe to notifications via Atom feed"
},
"quickActions": {
"notLoggedIn": "Please log in to the main Vikunja window first.",

View File

@ -117,6 +117,11 @@ const router = createRouter({
name: 'user.settings.data-export',
component: () => import('@/views/user/settings/DataExport.vue'),
},
{
path: '/user/settings/feeds',
name: 'user.settings.feeds',
component: () => import('@/views/user/settings/AtomFeed.vue'),
},
{
path: '/user/settings/deletion',
name: 'user.settings.deletion',

View File

@ -67,6 +67,10 @@ const navigationItems = computed(() => {
routeName: 'user.settings.caldav',
condition: caldavEnabled.value,
},
{
title: t('user.settings.feeds.title'),
routeName: 'user.settings.feeds',
},
{
title: t('user.settings.apiTokens.title'),
routeName: 'user.settings.apiTokens',

View File

@ -0,0 +1,75 @@
<template>
<Card :title="$t('user.settings.feeds.title')">
<p>
{{ $t('user.settings.feeds.howTo') }}
</p>
<FormField
v-model="feedUrl"
type="text"
readonly
>
<template #addon>
<XButton
v-tooltip="$t('misc.copy')"
:shadow="false"
icon="paste"
@click="copy(feedUrl)"
/>
</template>
</FormField>
<p class="mbs-4">
<i18n-t
keypath="user.settings.feeds.usernameIs"
scope="global"
>
<strong>{{ username }}</strong>
</i18n-t>
</p>
<p class="mbs-2">
<i18n-t
keypath="user.settings.feeds.apiTokenHint"
scope="global"
>
<template #scope>
<code>feeds:access</code>
</template>
<template #link>
<RouterLink
:to="{
name: 'user.settings.apiTokens',
query: {
title: $t('user.settings.feeds.tokenTitle'),
scopes: 'feeds:access',
},
}"
>
{{ $t('user.settings.apiTokens.title') }}
</RouterLink>
</template>
</i18n-t>
</p>
</Card>
</template>
<script lang="ts" setup>
import {computed} from 'vue'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@/composables/useTitle'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import FormField from '@/components/input/FormField.vue'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
const copy = useCopyToClipboard()
const {t} = useI18n({useScope: 'global'})
useTitle(() => `${t('user.settings.feeds.title')} - ${t('user.settings.title')}`)
const authStore = useAuthStore()
const configStore = useConfigStore()
const username = computed(() => authStore.info?.username)
const feedUrl = computed(() => `${configStore.apiBase}/feeds/notifications.atom`)
</script>