Files
mealie/frontend/app/components/Domain/Announcement/AnnouncementDialog.vue
2026-04-08 15:59:50 +00:00

123 lines
3.7 KiB
Vue

<template>
<BaseDialog
v-if="currentAnnouncement"
v-model="dialog"
:title="$t('announcements.announcements')"
:icon="$globals.icons.bullhornVariant"
:cancel-text="$t('general.done')"
width="100%"
max-width="1200"
>
<div class="d-flex">
<!-- Nav list -->
<v-list
nav
density="compact"
color="primary"
class="overflow-y-auto border-e flex-shrink-0"
style="width: 200px; height: 60vh"
>
<v-list-item
v-for="announcement in allAnnouncements.toReversed()"
:key="announcement.key"
:active="currentAnnouncement.key === announcement.key"
rounded
@click="setCurrentAnnouncement(announcement)"
>
<v-list-item-title class="text-body-2">
{{ announcement.meta?.title }}
</v-list-item-title>
<v-list-item-subtitle v-if="announcement.date">
{{ $d(announcement.date) }}
</v-list-item-subtitle>
<template v-if="newAnnouncements.some(a => a.key === announcement.key)" #append>
<v-icon size="x-small" color="info">
{{ $globals.icons.alertCircle }}
</v-icon>
</template>
</v-list-item>
</v-list>
<!-- Main content -->
<div class="flex-grow-1 overflow-y-auto" style="max-height: 60vh">
<v-card-title>
<v-chip v-if="currentAnnouncement.date" label large class="me-1">
<v-icon class="me-1">
{{ $globals.icons.calendar }}
</v-icon>
{{ $d(currentAnnouncement.date) }}
</v-chip>
{{ currentAnnouncement.meta?.title }}
</v-card-title>
<v-card-text>
<component :is="currentAnnouncement.component" />
</v-card-text>
</div>
</div>
<template #custom-card-action>
<BaseButton
v-if="newAnnouncements.length"
color="success"
:icon="$globals.icons.textBoxCheckOutline"
:text="$t('announcements.mark-all-as-read')"
@click="markAllAsRead"
/>
<BaseButton
:disabled="isLastAnnouncement(currentAnnouncement.key)"
color="info"
:icon="$globals.icons.arrowRightBold"
icon-right
:text="$t('general.next')"
@click="nextAnnouncement"
/>
</template>
</BaseDialog>
</template>
<script setup lang="ts">
import { useAnnouncements } from "~/composables/use-announcements";
import type { Announcement } from "~/composables/use-announcements";
const dialog = defineModel<boolean>({ default: false });
const route = useRoute();
watch(() => route.fullPath, () => { dialog.value = false; });
const { newAnnouncements, allAnnouncements, setLastRead, markAllAsRead } = useAnnouncements();
const currentAnnouncement = shallowRef<Announcement | undefined>();
watch(dialog, () => {
if (!dialog.value || currentAnnouncement.value) {
return;
}
// Show first unread on open, or fall back to the newest
const next = newAnnouncements.value.at(0) || allAnnouncements.at(-1)!;
setCurrentAnnouncement(next);
});
function setCurrentAnnouncement(announcement: Announcement) {
currentAnnouncement.value = announcement;
setLastRead(announcement.key);
}
function nextAnnouncement() {
// Find the first unread announcement after the current one (current is already removed from newAnnouncements)
const next = newAnnouncements.value.find(a => a.key > currentAnnouncement.value!.key);
if (next) {
setCurrentAnnouncement(next);
}
}
function isLastAnnouncement(key: string) {
if (!newAnnouncements.value.length) {
return true;
}
else {
return key >= newAnnouncements.value.at(-1)!.key;
}
}
</script>