Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,14 @@
"view": "Преглед",
"no_emails_found": "Няма намерени архивирани съобщения.",
"prev": "Предишен",
"next": "Следващ"
"next": "Следващ",
"delete_error_title": "Неуспешно изтриване на имейл",
"delete_selected": "Изтриване на избраните",
"bulk_delete_confirmation_title": "Сигурни ли сте, че искате да изтриете {{count}} избрани имейла?",
"bulk_delete_confirmation_description": "Това действие не може да бъде отменено и ще премахне завинаги избраните имейли и техните прикачени файлове.",
"deleting": "Изтриване",
"confirm": "Потвърждение",
"cancel": "Отказ"
},
"dashboard_page": {
"title": "Табло за управление",
Expand Down
10 changes: 8 additions & 2 deletions packages/frontend/src/lib/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,14 @@
"view": "Ansehen",
"no_emails_found": "Keine archivierten E-Mails gefunden.",
"prev": "Zurück",
"next": "Weiter"
"next": "Weiter",
"delete_error_title": "E-Mail konnte nicht gelöscht werden",
"delete_selected": "Ausgewählte löschen",
"bulk_delete_confirmation_title": "Möchten Sie wirklich {{count}} ausgewählte E-Mails löschen?",
"bulk_delete_confirmation_description": "Diese Aktion kann nicht rückgängig gemacht werden und entfernt die ausgewählten E-Mails und ihre Anhänge dauerhaft.",
"deleting": "Wird gelöscht",
"confirm": "Bestätigen",
"cancel": "Abbrechen"
},
"dashboard_page": {
"title": "Dashboard",
Expand Down Expand Up @@ -622,7 +629,6 @@
"id": "ID",
"name": "Name",
"state": "Status",

"created_at": "Erstellt am",
"processed_at": "Verarbeitet am",
"finished_at": "Beendet am",
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,14 @@
"view": "Προβολή",
"no_emails_found": "Δεν βρέθηκαν αρχειοθετημένα email.",
"prev": "Προηγούμενο",
"next": "Επόμενο"
"next": "Επόμενο",
"delete_error_title": "Αποτυχία διαγραφής email",
"delete_selected": "Διαγραφή επιλεγμένων",
"bulk_delete_confirmation_title": "Είστε βέβαιοι ότι θέλετε να διαγράψετε {{count}} επιλεγμένο(-α) email;",
"bulk_delete_confirmation_description": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί και θα αφαιρέσει οριστικά τα επιλεγμένα email και τα συνημμένα τους.",
"deleting": "Διαγραφή",
"confirm": "Επιβεβαίωση",
"cancel": "Ακύρωση"
},
"dashboard_page": {
"title": "Πίνακας ελέγχου",
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,14 @@
"view": "View",
"no_emails_found": "No archived emails found.",
"prev": "Prev",
"next": "Next"
"next": "Next",
"delete_error_title": "Failed to delete email",
"delete_selected": "Delete Selected",
"bulk_delete_confirmation_title": "Are you sure you want to delete {{count}} selected emails?",
"bulk_delete_confirmation_description": "This action cannot be undone and will permanently remove the selected emails and their attachments.",
"deleting": "Deleting",
"confirm": "Confirm",
"cancel": "Cancel"
},
"dashboard_page": {
"title": "Dashboard",
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,14 @@
"view": "Ver",
"no_emails_found": "No se encontraron correos electrónicos archivados.",
"prev": "Anterior",
"next": "Siguiente"
"next": "Siguiente",
"delete_error_title": "Error al eliminar el correo",
"delete_selected": "Eliminar seleccionados",
"bulk_delete_confirmation_title": "¿Está seguro de que desea eliminar {{count}} correos seleccionados?",
"bulk_delete_confirmation_description": "Esta acción no se puede deshacer y eliminará permanentemente los correos seleccionados y sus archivos adjuntos.",
"deleting": "Eliminando",
"confirm": "Confirmar",
"cancel": "Cancelar"
},
"dashboard_page": {
"title": "Tablero",
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/et.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,14 @@
"view": "Vaade",
"no_emails_found": "Arhiveeritud e-kirju ei leitud.",
"prev": "Eelmine",
"next": "Järgmine"
"next": "Järgmine",
"delete_error_title": "Meili kustutamine ebaõnnestus",
"delete_selected": "Kustuta valitud",
"bulk_delete_confirmation_title": "Kas olete kindel, et soovite kustutada {{count}} valitud meili?",
"bulk_delete_confirmation_description": "Seda toimingut ei saa tagasi võtta ja see eemaldab valitud meilid ja nende manused jäädavalt.",
"deleting": "Kustutamine",
"confirm": "Kinnita",
"cancel": "Tühista"
}
}
}
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,14 @@
"view": "Voir",
"no_emails_found": "Aucun e-mail archivé trouvé.",
"prev": "Préc",
"next": "Suiv"
"next": "Suiv",
"delete_error_title": "Impossible de supprimer l'e-mail",
"delete_selected": "Supprimer la sélection",
"bulk_delete_confirmation_title": "Voulez-vous vraiment supprimer {{count}} e-mails sélectionnés ?",
"bulk_delete_confirmation_description": "Cette action est irréversible et supprimera définitivement les e-mails sélectionnés et leurs pièces jointes.",
"deleting": "Suppression en cours",
"confirm": "Confirmer",
"cancel": "Annuler"
},
"dashboard_page": {
"title": "Tableau de bord",
Expand Down
10 changes: 8 additions & 2 deletions packages/frontend/src/lib/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,14 @@
"view": "Visualizza",
"no_emails_found": "Nessuna email archiviata trovata.",
"prev": "Prec",
"next": "Succ"
"next": "Succ",
"delete_error_title": "Impossibile eliminare l'email",
"delete_selected": "Elimina selezionati",
"bulk_delete_confirmation_title": "Sei sicuro di voler eliminare {{count}} email selezionate?",
"bulk_delete_confirmation_description": "Questa azione non può essere annullata e rimuoverà definitivamente le email selezionate e i loro allegati.",
"deleting": "Eliminazione in corso",
"confirm": "Conferma",
"cancel": "Annulla"
},
"dashboard_page": {
"title": "Dashboard",
Expand Down Expand Up @@ -358,7 +365,6 @@
"id": "ID",
"name": "Nome",
"state": "Stato",

"created_at": "Creato il",
"processed_at": "Elaborato il",
"finished_at": "Terminato il",
Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,14 @@
"view": "表示",
"no_emails_found": "アーカイブされたメールは見つかりませんでした。",
"prev": "前へ",
"next": "次へ"
"next": "次へ",
"delete_error_title": "メールの削除に失敗しました",
"delete_selected": "選択したものを削除",
"bulk_delete_confirmation_title": "選択した{{count}}件のメールを削除してもよろしいですか?",
"bulk_delete_confirmation_description": "この操作は取り消せません。選択したメールと添付ファイルは完全に削除されます。",
"deleting": "削除中",
"confirm": "確認",
"cancel": "キャンセル"
}
}
}
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,14 @@
"view": "Bekijken",
"no_emails_found": "Geen gearchiveerde e-mails gevonden.",
"prev": "Vorige",
"next": "Volgende"
"next": "Volgende",
"delete_error_title": "Verwijderen van e-mail mislukt",
"delete_selected": "Geselecteerde verwijderen",
"bulk_delete_confirmation_title": "Weet u zeker dat u {{count}} geselecteerde e-mails wilt verwijderen?",
"bulk_delete_confirmation_description": "Deze actie kan niet ongedaan worden gemaakt en verwijdert de geselecteerde e-mails en hun bijlagen definitief.",
"deleting": "Verwijderen",
"confirm": "Bevestigen",
"cancel": "Annuleren"
}
}
}
9 changes: 8 additions & 1 deletion packages/frontend/src/lib/translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,14 @@
"view": "Visualizar",
"no_emails_found": "Nenhum e-mail arquivado encontrado.",
"prev": "Anterior",
"next": "Próximo"
"next": "Próximo",
"delete_error_title": "Falha ao excluir e-mail",
"delete_selected": "Excluir selecionados",
"bulk_delete_confirmation_title": "Tem certeza que deseja excluir {{count}} e-mails selecionados?",
"bulk_delete_confirmation_description": "Esta ação não pode ser desfeita e removerá permanentemente os e-mails selecionados e seus anexos.",
"deleting": "Excluindo",
"confirm": "Confirmar",
"cancel": "Cancelar"
}
}
}
142 changes: 136 additions & 6 deletions packages/frontend/src/routes/dashboard/archived-emails/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,96 @@
import * as Table from '$lib/components/ui/table';
import { Button } from '$lib/components/ui/button';
import * as Select from '$lib/components/ui/select';
import * as Dialog from '$lib/components/ui/dialog';
import { Checkbox } from '$lib/components/ui/checkbox';
import { goto } from '$app/navigation';
import { t } from '$lib/translations';
import * as Pagination from '$lib/components/ui/pagination/index.js';
import ChevronLeft from 'lucide-svelte/icons/chevron-left';
import ChevronRight from 'lucide-svelte/icons/chevron-right';
import { api } from '$lib/api.client';
import { setAlert } from '$lib/components/custom/alert/alert-state.svelte';
import type { PaginatedArchivedEmails } from '@open-archiver/types';

let { data }: { data: PageData } = $props();

let ingestionSources = $derived(data.ingestionSources);
let archivedEmails = $derived(data.archivedEmails);
let archivedEmails = $state<PaginatedArchivedEmails>(data.archivedEmails);
let selectedIngestionSourceId = $derived(data.selectedIngestionSourceId);

let selectedIds = $state<string[]>([]);
let isBulkDeleteDialogOpen = $state(false);
let isBulkDeleting = $state(false);

// Keep archivedEmails in sync with navigation; preserve selectedIds across
// pages but reset when the source changes.
let previousSourceId = $state(data.selectedIngestionSourceId);
$effect(() => {
archivedEmails = data.archivedEmails;
if (data.selectedIngestionSourceId !== previousSourceId) {
selectedIds = [];
previousSourceId = data.selectedIngestionSourceId;
}
});

const allEmailIds = $derived(archivedEmails.items.map((e) => e.id));
const allCurrentPageSelected = $derived(
allEmailIds.length > 0 && allEmailIds.every((id) => selectedIds.includes(id))
);
const someCurrentPageSelected = $derived(allEmailIds.some((id) => selectedIds.includes(id)));

const handleSourceChange = (value: string | undefined) => {
if (value) {
goto(`/dashboard/archived-emails?ingestionSourceId=${value}`);
}
};

const confirmBulkDelete = async () => {
isBulkDeleting = true;
const deletedIds: string[] = [];
try {
for (const id of selectedIds) {
const res = await api(`/archived-emails/${id}`, { method: 'DELETE' });
if (!res.ok) {
const errorBody = await res.json();
selectedIds = selectedIds.filter((i) => !deletedIds.includes(i));
isBulkDeleteDialogOpen = false;
setAlert({
type: 'error',
title: $t('app.archived_emails_page.delete_error_title'),
message: errorBody.message || JSON.stringify(errorBody),
duration: 5000,
show: true,
});
return;
}
deletedIds.push(id);
}
selectedIds = [];
isBulkDeleteDialogOpen = false;
await goto(
`/dashboard/archived-emails${selectedIngestionSourceId ? `?ingestionSourceId=${selectedIngestionSourceId}` : ''}`,
{ invalidateAll: true }
);
} finally {
isBulkDeleting = false;
}
};
</script>

<svelte:head>
<title>{$t('app.archived_emails_page.title')} - OpenArchiver</title>
</svelte:head>

<div class="mb-4 flex items-center justify-between">
<h1 class="text-2xl font-bold">{$t('app.archived_emails_page.header')}</h1>
<div class="flex items-center gap-4">
<h1 class="text-2xl font-bold">{$t('app.archived_emails_page.header')}</h1>
{#if selectedIds.length > 0}
<Button variant="destructive" onclick={() => (isBulkDeleteDialogOpen = true)}>
{$t('app.archived_emails_page.delete_selected')} ({selectedIds.length})
</Button>
{/if}
</div>
{#if ingestionSources.length > 0}
<div class="w-[250px]">
<Select.Root
Expand Down Expand Up @@ -56,6 +121,24 @@
<Table.Root>
<Table.Header>
<Table.Row>
<!-- plain <th> to avoid shadcn stripping right padding on checkbox cells -->
<th class="text-foreground h-10 w-12 px-2 text-left align-middle font-medium">
<Checkbox
onCheckedChange={(checked) => {
if (checked) {
selectedIds = [
...selectedIds,
...allEmailIds.filter((id) => !selectedIds.includes(id)),
];
} else {
selectedIds = selectedIds.filter((id) => !allEmailIds.includes(id));
}
}}
checked={allCurrentPageSelected
? true
: ((someCurrentPageSelected ? 'indeterminate' : false) as any)}
/>
</th>
<Table.Head>{$t('app.archived_emails_page.date')}</Table.Head>
<Table.Head>{$t('app.archived_emails_page.subject')}</Table.Head>
<Table.Head>{$t('app.archived_emails_page.sender')}</Table.Head>
Expand All @@ -68,6 +151,18 @@
{#if archivedEmails.items.length > 0}
{#each archivedEmails.items as email (email.id)}
<Table.Row>
<Table.Cell>
<Checkbox
checked={selectedIds.includes(email.id)}
onCheckedChange={() => {
if (selectedIds.includes(email.id)) {
selectedIds = selectedIds.filter((id) => id !== email.id);
} else {
selectedIds = [...selectedIds, email.id];
}
}}
/>
</Table.Cell>
<Table.Cell>{new Date(email.sentAt).toLocaleString()}</Table.Cell>

<Table.Cell>
Expand All @@ -90,16 +185,16 @@
</Table.Cell>
<Table.Cell class="text-right">
<a href={`/dashboard/archived-emails/${email.id}`}>
<Button variant="outline"
>{$t('app.archived_emails_page.view')}</Button
>
<Button variant="outline">
{$t('app.archived_emails_page.view')}
</Button>
</a>
</Table.Cell>
</Table.Row>
{/each}
{:else}
<Table.Row>
<Table.Cell colspan={5} class="text-center"
<Table.Cell colspan={7} class="text-center"
>{$t('app.archived_emails_page.no_emails_found')}</Table.Cell
>
</Table.Row>
Expand Down Expand Up @@ -167,3 +262,38 @@
</Pagination.Root>
</div>
{/if}

<!-- Bulk delete confirmation -->
<Dialog.Root bind:open={isBulkDeleteDialogOpen}>
<Dialog.Content class="sm:max-w-lg">
<Dialog.Header>
<Dialog.Title>
{$t('app.archived_emails_page.bulk_delete_confirmation_title', {
count: selectedIds.length,
} as any)}
</Dialog.Title>
<Dialog.Description>
{$t('app.archived_emails_page.bulk_delete_confirmation_description')}
</Dialog.Description>
</Dialog.Header>
<Dialog.Footer class="sm:justify-start">
<Button
type="button"
variant="destructive"
onclick={confirmBulkDelete}
disabled={isBulkDeleting}
>
{#if isBulkDeleting}
{$t('app.archived_emails_page.deleting')}...
{:else}
{$t('app.archived_emails_page.confirm')}
{/if}
</Button>
<Dialog.Close>
<Button type="button" variant="secondary">
{$t('app.archived_emails_page.cancel')}
</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
Loading
Loading