announcements

This commit is contained in:
Gauthier Roebroeck 2025-05-20 15:16:47 +08:00
parent 021d12b4f0
commit 598db5a814
12 changed files with 192 additions and 20 deletions

View file

@ -0,0 +1,16 @@
import {defineMutation, useMutation, useQueryCache} from '@pinia/colada'
import {komgaClient} from '@/api/komga-client'
export const useMarkAnnouncementsRead = defineMutation(() => {
const queryCache = useQueryCache()
return useMutation({
mutation: (announcementIds: string[]) =>
komgaClient.PUT('/api/v1/announcements', {body: announcementIds}),
onSuccess: () => {
queryCache.invalidateQueries({key: ['announcements']})
},
onError: (error) => {
console.log('announcements mark read error', error)
},
})
})

View file

@ -1,8 +1,8 @@
import {useQuery} from '@pinia/colada'
import {defineQuery, useQuery} from '@pinia/colada'
import {komgaClient} from '@/api/komga-client'
import type {ActuatorInfo} from '@/types/Actuator'
export function useActuatorInfo() {
export const useActuatorInfo = defineQuery(() => {
return useQuery({
key: () => ['actuator-info'],
query: () => komgaClient.GET('/actuator/info')
@ -12,4 +12,4 @@ export function useActuatorInfo() {
staleTime: 60 * 60 * 1000,
gcTime: false,
})
}
})

View file

@ -0,0 +1,21 @@
import {defineQuery, useQuery} from '@pinia/colada'
import {komgaClient} from '@/api/komga-client'
export const useAnnouncements = defineQuery(() => {
const {data, ...rest} = useQuery({
key: () => ['announcements'],
query: () => komgaClient.GET('/api/v1/announcements')
// unwrap the openapi-fetch structure on success
.then((res) => res.data),
// 1 hour
staleTime: 60 * 60 * 1000,
gcTime: false,
})
const unreadCount = computed(() => data.value?.items
?.filter((x) => false == x._komga?.read)
?.length || 0
)
return {...rest, data, unreadCount}
})

View file

@ -1,7 +1,7 @@
import {useQuery} from '@pinia/colada'
import {defineQuery, useQuery} from '@pinia/colada'
import {komgaClient} from '@/api/komga-client'
export function useAppReleases() {
export const useAppReleases = defineQuery(() => {
return useQuery({
key: () => ['app-releases'],
query: () => komgaClient.GET('/api/v1/releases')
@ -11,4 +11,4 @@ export function useAppReleases() {
staleTime: 60 * 60 * 1000,
gcTime: false,
})
}
})

View file

@ -1,7 +1,7 @@
import {useQuery} from '@pinia/colada'
import {defineQuery, useQuery} from '@pinia/colada'
import {komgaClient} from '@/api/komga-client'
export function useCurrentUser() {
export const useCurrentUser = defineQuery(() => {
return useQuery({
key: () => ['current-user'],
query: () => komgaClient.GET('/api/v2/users/me')
@ -12,4 +12,4 @@ export function useCurrentUser() {
gcTime: false,
autoRefetch: true,
})
}
})

View file

@ -21,6 +21,7 @@ declare module 'vue' {
AppFooter: typeof import('./components/AppFooter.vue')['default']
BuidVersion: typeof import('./components/BuidVersion.vue')['default']
BuildCommit: typeof import('./components/BuildCommit.vue')['default']
BuildVersion: typeof import('./components/BuildVersion.vue')['default']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
LoginForm: typeof import('./components/LoginForm.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View file

@ -4,7 +4,7 @@
<div class="d-flex align-center text-caption text-medium-emphasis pa-2">
<div class="d-flex ms-auto">
<BuildCommit class="me-2" />
<BuidVersion />
<BuildVersion />
</div>
</div>
</template>

View file

@ -1,11 +1,25 @@
<template>
<v-list-group value="Server">
<v-list-group
ref="group"
value="Server"
>
<template #activator="{ props }">
<v-list-item
v-bind="props"
title="Server"
prepend-icon="mdi-cog"
/>
>
<template #prepend>
<v-badge
:model-value="unreadCount > 0 && !$refs.group.isOpen"
dot
floating
color="info"
>
<v-icon>mdi-cog</v-icon>
</v-badge>
</template>
</v-list-item>
</template>
<v-list-item
@ -24,13 +38,30 @@
to="/server/metrics"
title="Metrics"
/>
<v-list-item
to="/server/announcements"
title="Announcements"
/>
>
<template #append>
<v-badge
:model-value="unreadCount > 0"
:content="unreadCount"
inline
color="info"
/>
</template>
</v-list-item>
<v-list-item
to="/server/updates"
title="Updates"
/>
</v-list-group>
</template>
<script setup lang="ts">
import {useAnnouncements} from '@/colada/queries/announcements.ts'
const {unreadCount} = useAnnouncements()
</script>

View file

@ -2,7 +2,7 @@ import {useAppReleases} from '@/colada/queries/app-releases.ts'
import {useBuildVersion} from '@/composables/buid-version.ts'
export function useLatestVersion() {
const {data, } = useAppReleases()
const {data } = useAppReleases()
const {buildVersion} = useBuildVersion()
const latestRelease = computed(() => data.value?.find(x => x.latest))

View file

@ -1,11 +1,114 @@
<template>
<h1>Announcements</h1>
<v-alert
v-if="error"
type="error"
variant="tonal"
>
Error loading data
</v-alert>
<template v-if="data">
<div
v-for="(item, index) in data.items"
:key="index"
>
<v-row
justify="space-between"
align="center"
>
<v-col cols="auto">
<div class="ml-n2">
<a
:href="item.url"
target="_blank"
class="text-h3 font-weight-medium link-underline"
>{{ item.title }}</a>
</div>
<div class="mt-2 subtitle-1">
{{ item.date_modified }}
</div>
</v-col>
<v-col cols="auto">
<v-tooltip
text="Mark as read"
:disabled="item._komga.read"
>
<template #activator="{ props }">
<v-fab
v-bind="props"
icon="mdi-check"
elevation="3"
color="success"
variant="tonal"
size="small"
:disabled="item._komga.read"
@click="markRead(item.id)"
/>
</template>
</v-tooltip>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<!-- eslint-disable vue/no-v-html -->
<div
class="announcement"
v-html="item.content_html"
/>
<!-- eslint-enable vue/no-v-html -->
</v-col>
</v-row>
<v-divider
v-if="index != data.items.length - 1"
class="my-8"
/>
</div>
<v-fab
:active="unreadCount > 0"
color="success"
location="bottom right"
app
icon="mdi-check-all"
size="x-large"
class="ms-n5"
@click="markAllRead()"
/>
</template>
</template>
<script lang="ts" setup>
//
import {useAnnouncements} from '@/colada/queries/announcements.ts'
import {useMarkAnnouncementsRead} from '@/colada/mutations/mark-announcements-read.ts'
const {data, error, unreadCount} = useAnnouncements()
const {mutate} = useMarkAnnouncementsRead()
function markAllRead() {
mutate(data.value.items.map(x => x.id))
}
function markRead(id: string) {
mutate([id])
}
</script>
<style lang="scss">
.announcement p {
margin-bottom: 16px;
}
.announcement ul {
padding-left: 24px;
}
.announcement a {
color: var(--v-anchor-base);
}
</style>
<route lang="yaml">
meta:
requiresRole: ADMIN

View file

@ -75,7 +75,7 @@
<v-col cols="12">
<!-- eslint-disable vue/no-v-html -->
<div
class="releases"
class="release"
v-html="marked(release.description)"
/>
<!-- eslint-enable vue/no-v-html -->
@ -103,15 +103,15 @@ const {isLatestVersion, latestRelease: latest} = useLatestVersion()
</script>
<style lang="scss">
.releases p {
.release p {
margin-bottom: 16px;
}
.releases ul {
.release ul {
padding-left: 24px;
}
.releases a {
.release a {
color: var(--v-anchor-base);
}
</style>