frontend: Responsive design for menu bar, pagination

This commit is contained in:
Wayne
2025-10-18 18:30:27 +02:00
parent b1576eb152
commit 874fafd0f3
19 changed files with 640 additions and 435 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "open-archiver-enterprise-app",
"version": "1.0.0",
"version": "1.4.0",
"private": true,
"scripts": {
"dev": "ts-node-dev -r tsconfig-paths/register --project tsconfig.json --respawn --transpile-only index.ts",

View File

@@ -1,8 +1,8 @@
{
"name": "open-archiver",
"version": "0.3.4",
"version": "0.4.0",
"private": true,
"license": "SEE LICENSE IN LICENSE-AGPL.txt",
"license": "SEE LICENSE IN LICENSE file",
"scripts": {
"build:oss": "pnpm --filter \"./packages/*\" --filter \"!./packages/enterprise\" --filter \"./apps/open-archiver\" build",
"build:enterprise": "cross-env VITE_ENTERPRISE_MODE=true pnpm build",

View File

@@ -2,7 +2,7 @@
"name": "@open-archiver/enterprise",
"version": "1.0.0",
"private": true,
"license": "SEE LICENSE IN LICENSE.txt",
"license": "SEE LICENSE IN LICENSE-BSL.txt",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {

View File

@@ -17,7 +17,6 @@
"@iconify/svelte": "^5.0.1",
"@open-archiver/types": "workspace:*",
"@sveltejs/kit": "^2.38.1",
"bits-ui": "^2.8.10",
"clsx": "^2.1.1",
"d3-shape": "^3.2.0",
"html-entities": "^2.6.0",
@@ -32,13 +31,14 @@
},
"devDependencies": {
"@internationalized/date": "^3.8.2",
"@lucide/svelte": "^0.515.0",
"@lucide/svelte": "^0.544.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/adapter-node": "^5.2.13",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.0.0",
"@types/d3-shape": "^3.1.7",
"@types/semver": "^7.7.1",
"bits-ui": "^2.12.0",
"dotenv": "^17.2.0",
"layerchart": "2.0.0-next.27",
"mode-watcher": "^1.1.0",

View File

@@ -0,0 +1,25 @@
import Root from "./pagination.svelte";
import Content from "./pagination-content.svelte";
import Item from "./pagination-item.svelte";
import Link from "./pagination-link.svelte";
import PrevButton from "./pagination-prev-button.svelte";
import NextButton from "./pagination-next-button.svelte";
import Ellipsis from "./pagination-ellipsis.svelte";
export {
Root,
Content,
Item,
Link,
PrevButton,
NextButton,
Ellipsis,
//
Root as Pagination,
Content as PaginationContent,
Item as PaginationItem,
Link as PaginationLink,
PrevButton as PaginationPrevButton,
NextButton as PaginationNextButton,
Ellipsis as PaginationEllipsis,
};

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLUListElement>> = $props();
</script>
<ul
bind:this={ref}
data-slot="pagination-content"
class={cn("flex flex-row items-center gap-1", className)}
{...restProps}
>
{@render children?.()}
</ul>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
</script>
<span
bind:this={ref}
aria-hidden="true"
data-slot="pagination-ellipsis"
class={cn("flex size-9 items-center justify-center", className)}
{...restProps}
>
<EllipsisIcon class="size-4" />
<span class="sr-only">More pages</span>
</span>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import type { HTMLLiAttributes } from "svelte/elements";
import type { WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li bind:this={ref} data-slot="pagination-item" {...restProps}>
{@render children?.()}
</li>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { type Props, buttonVariants } from "$lib/components/ui/button/index.js";
let {
ref = $bindable(null),
class: className,
size = "icon",
isActive,
page,
children,
...restProps
}: PaginationPrimitive.PageProps &
Props & {
isActive: boolean;
} = $props();
</script>
{#snippet Fallback()}
{page.value}
{/snippet}
<PaginationPrimitive.Page
bind:ref
{page}
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
class={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
children={children || Fallback}
{...restProps}
/>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: PaginationPrimitive.NextButtonProps = $props();
</script>
{#snippet Fallback()}
<span>Next</span>
<ChevronRightIcon class="size-4" />
{/snippet}
<PaginationPrimitive.NextButton
bind:ref
aria-label="Go to next page"
class={cn(
buttonVariants({
size: "default",
variant: "ghost",
class: "gap-1 px-2.5 sm:pr-2.5",
}),
className
)}
children={children || Fallback}
{...restProps}
/>

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: PaginationPrimitive.PrevButtonProps = $props();
</script>
{#snippet Fallback()}
<ChevronLeftIcon class="size-4" />
<span>Previous</span>
{/snippet}
<PaginationPrimitive.PrevButton
bind:ref
aria-label="Go to previous page"
class={cn(
buttonVariants({
size: "default",
variant: "ghost",
class: "gap-1 px-2.5 sm:pl-2.5",
}),
className
)}
children={children || Fallback}
{...restProps}
/>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
count = 0,
perPage = 10,
page = $bindable(1),
siblingCount = 1,
...restProps
}: PaginationPrimitive.RootProps = $props();
</script>
<PaginationPrimitive.Root
bind:ref
bind:page
role="navigation"
aria-label="pagination"
data-slot="pagination"
class={cn("mx-auto flex w-full justify-center", className)}
{count}
{perPage}
{siblingCount}
{...restProps}
/>

View File

@@ -1,7 +1,9 @@
<script lang="ts">
import * as NavigationMenu from '$lib/components/ui/navigation-menu/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import Button from '$lib/components/ui/button/button.svelte';
import { authStore } from '$lib/stores/auth.store';
import { Menu } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import ThemeSwitcher from '$lib/components/custom/ThemeSwitcher.svelte';
@@ -20,13 +22,19 @@
const baseNavItems: NavItem[] = [
{ href: '/dashboard', label: $t('app.layout.dashboard'), position: 0 },
{ href: '/dashboard/ingestions', label: $t('app.layout.ingestions'), position: 1 },
{
href: '/dashboard/archived-emails',
label: $t('app.layout.archived_emails'),
position: 2,
label: $t('app.archive.title'),
subMenu: [
{ href: '/dashboard/ingestions', label: $t('app.layout.ingestions') },
{
href: '/dashboard/archived-emails',
label: $t('app.layout.archived_emails'),
},
],
position: 1,
},
{ href: '/dashboard/search', label: $t('app.layout.search'), position: 3 },
{ href: '/dashboard/search', label: $t('app.layout.search'), position: 2 },
{
label: $t('app.layout.admin'),
subMenu: [
@@ -43,7 +51,7 @@
label: $t('app.layout.roles'),
},
],
position: 5,
position: 4,
},
{
label: $t('app.layout.settings'),
@@ -57,7 +65,7 @@
label: $t('app.layout.api_keys'),
},
],
position: 6,
position: 5,
},
];
@@ -65,13 +73,42 @@
{
label: 'Compliance',
subMenu: [{ href: '/dashboard/compliance/audit-log', label: 'Audit Log' }],
position: 4,
position: 3,
},
];
function mergeNavItems(baseItems: NavItem[], enterpriseItems: NavItem[]): NavItem[] {
const mergedItemsMap = new Map<number, NavItem>();
for (const item of baseItems) {
mergedItemsMap.set(item.position, {
...item,
subMenu: item.subMenu ? [...item.subMenu] : undefined,
});
}
for (const enterpriseItem of enterpriseItems) {
const existingItem = mergedItemsMap.get(enterpriseItem.position);
if (existingItem) {
if (existingItem.subMenu && enterpriseItem.subMenu) {
existingItem.subMenu = [...existingItem.subMenu, ...enterpriseItem.subMenu];
}
} else {
mergedItemsMap.set(enterpriseItem.position, {
...enterpriseItem,
subMenu: enterpriseItem.subMenu ? [...enterpriseItem.subMenu] : undefined,
});
}
}
const mergedItems = Array.from(mergedItemsMap.values());
return mergedItems.sort((a, b) => a.position - b.position);
}
let navItems: NavItem[] = $state(baseNavItems);
if (data.enterpriseMode) {
navItems = [...baseNavItems, ...enterpriseNavItems].sort((a, b) => a.position - b.position);
navItems = mergeNavItems(baseNavItems, enterpriseNavItems);
}
function handleLogout() {
authStore.logout();
@@ -79,59 +116,100 @@
}
</script>
<header class="bg-background sticky top-0 z-40 border-b">
<header class="bg-background sticky top-0 z-40 border-b px-4 md:px-0">
<div class="container mx-auto flex h-16 flex-row items-center justify-between">
<a href="/dashboard" class="flex flex-row items-center gap-2 font-bold">
<img src="/logos/logo-sq.svg" alt="OpenArchiver Logo" class="h-8 w-8" />
<span>Open Archiver</span>
<span class="hidden sm:inline-block">Open Archiver</span>
</a>
<NavigationMenu.Root viewport={false}>
<NavigationMenu.List class="flex items-center space-x-4">
{#each navItems as item}
{#if item.subMenu && item.subMenu.length > 0}
<NavigationMenu.Item
class={item.subMenu.some((sub) =>
page.url.pathname.startsWith(
sub.href.substring(0, sub.href.lastIndexOf('/'))
<!-- Desktop Navigation -->
<div class="hidden lg:flex">
<NavigationMenu.Root viewport={false}>
<NavigationMenu.List class="flex items-center space-x-4">
{#each navItems as item}
{#if item.subMenu && item.subMenu.length > 0}
<NavigationMenu.Item
class={item.subMenu.some((sub) =>
page.url.pathname.startsWith(
sub.href.substring(0, sub.href.lastIndexOf('/'))
)
)
)
? 'bg-accent rounded-md'
: ''}
>
<NavigationMenu.Trigger class="cursor-pointer font-normal">
{item.label}
</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-fit min-w-28 gap-1 p-1">
{#each item.subMenu as subItem}
<li>
<NavigationMenu.Link href={subItem.href}>
{subItem.label}
</NavigationMenu.Link>
</li>
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
{:else if item.href}
<NavigationMenu.Item
class={page.url.pathname === item.href ? 'bg-accent rounded-md' : ''}
>
<NavigationMenu.Link href={item.href}>
{item.label}
</NavigationMenu.Link>
</NavigationMenu.Item>
{/if}
{/each}
</NavigationMenu.List>
</NavigationMenu.Root>
? 'bg-accent rounded-md'
: ''}
>
<NavigationMenu.Trigger class="cursor-pointer font-normal">
{item.label}
</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-fit min-w-32 gap-1 p-1">
{#each item.subMenu as subItem}
<li>
<NavigationMenu.Link href={subItem.href}>
{subItem.label}
</NavigationMenu.Link>
</li>
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
{:else if item.href}
<NavigationMenu.Item
class={page.url.pathname === item.href
? 'bg-accent rounded-md'
: ''}
>
<NavigationMenu.Link href={item.href}>
{item.label}
</NavigationMenu.Link>
</NavigationMenu.Item>
{/if}
{/each}
</NavigationMenu.List>
</NavigationMenu.Root>
</div>
<div class="flex items-center gap-4">
<!-- Mobile Navigation -->
<div class="lg:hidden">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button {...props} variant="ghost" size="icon">
<Menu class="h-6 w-6" />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56" align="end">
{#each navItems as item}
{#if item.subMenu && item.subMenu.length > 0}
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>{item.label}</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
{#each item.subMenu as subItem}
<a href={subItem.href}>
<DropdownMenu.Item
>{subItem.label}</DropdownMenu.Item
>
</a>
{/each}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
{:else if item.href}
<a href={item.href}>
<DropdownMenu.Item>{item.label}</DropdownMenu.Item>
</a>
{/if}
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<ThemeSwitcher />
<Button onclick={handleLogout} variant="outline">{$t('app.layout.logout')}</Button>
</div>
</div>
</header>
<main class="container mx-auto my-10">
<main class="container mx-auto my-10 px-4 md:px-0">
{@render children()}
</main>

View File

@@ -18,7 +18,7 @@
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{#each queues as queue}
<a href={`/dashboard/admin/jobs/${queue.name}`} class="block">
<Card.Root class="hover:border-primary">
<Card.Root class=" hover:shadow-md">
<Card.Header>
<Card.Title class="capitalize">{queue.name.split('_').join(' ')}</Card.Title
>

View File

@@ -2,11 +2,15 @@
import type { PageData } from './$types';
import * as Card from '$lib/components/ui/card';
import { t } from '$lib/translations';
import { Badge } from '$lib/components/ui/badge';
import * as Table from '$lib/components/ui/table';
import { Button, buttonVariants } from '$lib/components/ui/button';
import { Button } from '$lib/components/ui/button';
import { goto } from '$app/navigation';
import type { JobStatus } from '@open-archiver/types';
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 { onMount } from 'svelte';
import { browser } from '$app/environment';
let { data }: { data: PageData } = $props();
let queue = $derived(data.queue);
@@ -22,6 +26,16 @@
let selectedStatus: JobStatus | undefined = $state('failed');
onMount(() => {
if (browser) {
const url = new URL(window.location.href);
const status = url.searchParams.get('status') as JobStatus;
if (status) {
selectedStatus = status;
}
}
});
function handleStatusChange(status: JobStatus) {
selectedStatus = status;
const url = new URL(window.location.href);
@@ -29,12 +43,6 @@
url.searchParams.set('page', '1');
goto(url.toString(), { invalidateAll: true });
}
function handlePageChange(page: number) {
const url = new URL(window.location.href);
url.searchParams.set('page', page.toString());
goto(url.toString(), { invalidateAll: true });
}
</script>
<svelte:head>
@@ -132,32 +140,72 @@
</Table.Body>
</Table.Root>
</Card.Content>
<Card.Footer class="flex justify-between">
<div>
<p class="text-muted-foreground text-sm">
{$t('app.jobs.showing')}
{queue.jobs.length}
{$t('app.jobs.of')}
{queue.pagination.totalJobs}
{$t('app.jobs.jobs')}
</p>
<Card.Footer class="flex flex-col items-center justify-between gap-4 sm:flex-row">
<div class="text-muted-foreground text-nowrap text-sm">
{$t('app.jobs.showing')}
{queue.jobs.length}
{$t('app.jobs.of')}
{queue.pagination.totalJobs}
{$t('app.jobs.jobs')}
</div>
<div class="flex space-x-2">
<Button
variant="outline"
disabled={queue.pagination.currentPage <= 1}
onclick={() => handlePageChange(queue.pagination.currentPage - 1)}
{#if queue.pagination.totalJobs > queue.pagination.limit}
<Pagination.Root
count={queue.pagination.totalJobs}
perPage={queue.pagination.limit}
page={queue.pagination.currentPage}
>
{$t('app.jobs.previous')}
</Button>
<Button
variant="outline"
disabled={queue.pagination.currentPage >= queue.pagination.totalPages}
onclick={() => handlePageChange(queue.pagination.currentPage + 1)}
>
{$t('app.jobs.next')}
</Button>
</div>
{#snippet children({ pages, currentPage })}
<Pagination.Content>
<Pagination.Item>
<a
href={`/dashboard/admin/jobs/${queue.name}?status=${selectedStatus}&page=${
currentPage - 1
}`}
>
<Pagination.PrevButton>
<ChevronLeft class="h-4 w-4" />
<span class="hidden sm:block"
>{$t('app.jobs.previous')}</span
>
</Pagination.PrevButton>
</a>
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<a
href={`/dashboard/admin/jobs/${queue.name}?status=${selectedStatus}&page=${page.value}`}
>
<Pagination.Link
{page}
isActive={currentPage === page.value}
>
{page.value}
</Pagination.Link>
</a>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<a
href={`/dashboard/admin/jobs/${queue.name}?status=${selectedStatus}&page=${
currentPage + 1
}`}
>
<Pagination.NextButton>
<span class="hidden sm:block">{$t('app.jobs.next')}</span>
<ChevronRight class="h-4 w-4" />
</Pagination.NextButton>
</a>
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
{/if}
</Card.Footer>
</Card.Root>
</div>

View File

@@ -5,6 +5,9 @@
import * as Select from '$lib/components/ui/select';
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';
let { data }: { data: PageData } = $props();
@@ -17,55 +20,6 @@
goto(`/dashboard/archived-emails?ingestionSourceId=${value}`);
}
};
const getPaginationItems = (currentPage: number, totalPages: number, siblingCount = 1) => {
const totalPageNumbers = siblingCount + 5;
if (totalPages <= totalPageNumbers) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex < totalPages - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPages;
if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingCount;
let leftRange = Array.from({ length: leftItemCount }, (_, i) => i + 1);
return [...leftRange, '...', totalPages];
}
if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 * siblingCount;
let rightRange = Array.from(
{ length: rightItemCount },
(_, i) => totalPages - rightItemCount + i + 1
);
return [firstPageIndex, '...', ...rightRange];
}
if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = Array.from(
{ length: rightSiblingIndex - leftSiblingIndex + 1 },
(_, i) => leftSiblingIndex + i
);
return [firstPageIndex, '...', ...middleRange, '...', lastPageIndex];
}
return [];
};
let paginationItems = $derived(
getPaginationItems(
archivedEmails.page,
Math.ceil(archivedEmails.total / archivedEmails.limit)
)
);
</script>
<svelte:head>
@@ -155,46 +109,61 @@
</div>
{#if archivedEmails.total > archivedEmails.limit}
<div class="mt-8 flex flex-row items-center justify-center space-x-2">
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${
archivedEmails.page - 1
}&limit=${archivedEmails.limit}`}
class={archivedEmails.page === 1 ? 'pointer-events-none' : ''}
<div class="mt-8">
<Pagination.Root
count={archivedEmails.total}
perPage={archivedEmails.limit}
page={archivedEmails.page}
>
<Button variant="outline" disabled={archivedEmails.page === 1}
>{$t('app.archived_emails_page.prev')}</Button
>
</a>
{#each paginationItems as item}
{#if typeof item === 'number'}
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${item}&limit=${archivedEmails.limit}`}
>
<Button variant={item === archivedEmails.page ? 'default' : 'outline'}
>{item}</Button
>
</a>
{:else}
<span class="px-4 py-2">...</span>
{/if}
{/each}
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${
archivedEmails.page + 1
}&limit=${archivedEmails.limit}`}
class={archivedEmails.page === Math.ceil(archivedEmails.total / archivedEmails.limit)
? 'pointer-events-none'
: ''}
>
<Button
variant="outline"
disabled={archivedEmails.page ===
Math.ceil(archivedEmails.total / archivedEmails.limit)}
>{$t('app.archived_emails_page.next')}</Button
>
</a>
{#snippet children({ pages, currentPage })}
<Pagination.Content>
<Pagination.Item>
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${
currentPage - 1
}&limit=${archivedEmails.limit}`}
>
<Pagination.PrevButton>
<ChevronLeft class="h-4 w-4" />
<span class="hidden sm:block"
>{$t('app.archived_emails_page.prev')}</span
>
</Pagination.PrevButton>
</a>
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${page.value}&limit=${archivedEmails.limit}`}
>
<Pagination.Link {page} isActive={currentPage === page.value}>
{page.value}
</Pagination.Link>
</a>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<a
href={`/dashboard/archived-emails?ingestionSourceId=${selectedIngestionSourceId}&page=${
currentPage + 1
}&limit=${archivedEmails.limit}`}
>
<Pagination.NextButton>
<span class="hidden sm:block"
>{$t('app.archived_emails_page.next')}</span
>
<ChevronRight class="h-4 w-4" />
</Pagination.NextButton>
</a>
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}

View File

@@ -14,58 +14,15 @@
import type { AuditLogAction, AuditLogEntry } from '@open-archiver/types';
import * as Dialog from '$lib/components/ui/dialog/index.js';
import { Label } from '$lib/components/ui/label';
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';
let { data }: { data: PageData } = $props();
let logs = $derived(data.logs);
let meta = $derived(data.meta);
const getPaginationItems = (currentPage: number, totalPages: number, siblingCount = 1) => {
const totalPageNumbers = siblingCount + 5;
if (totalPages <= totalPageNumbers) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex < totalPages - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPages;
if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingCount;
let leftRange = Array.from({ length: leftItemCount }, (_, i) => i + 1);
return [...leftRange, '...', totalPages];
}
if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 * siblingCount;
let rightRange = Array.from(
{ length: rightItemCount },
(_, i) => totalPages - rightItemCount + i + 1
);
return [firstPageIndex, '...', ...rightRange];
}
if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = Array.from(
{ length: rightSiblingIndex - leftSiblingIndex + 1 },
(_, i) => leftSiblingIndex + i
);
return [firstPageIndex, '...', ...middleRange, '...', lastPageIndex];
}
return [];
};
let paginationItems = $derived(
getPaginationItems(meta.page, Math.ceil(meta.total / meta.limit))
);
let isDetailViewOpen = $state(false);
let selectedLog = $state<AuditLogEntry | null>(null);
@@ -228,32 +185,54 @@
</div>
{#if meta.total > meta.limit}
<div class="mt-8 flex flex-row items-center justify-center space-x-2">
<a
href={`/dashboard/compliance/audit-log?page=${meta.page - 1}&limit=${meta.limit}`}
class={meta.page === 1 ? 'pointer-events-none' : ''}
>
<Button variant="outline" disabled={meta.page === 1}>{$t('app.audit_log.prev')}</Button>
</a>
{#each paginationItems as item}
{#if typeof item === 'number'}
<a href={`/dashboard/compliance/audit-log?page=${item}&limit=${meta.limit}`}>
<Button variant={item === meta.page ? 'default' : 'outline'}>{item}</Button>
</a>
{:else}
<span class="px-4 py-2">...</span>
{/if}
{/each}
<a
href={`/dashboard/compliance/audit-log?page=${meta.page + 1}&limit=${meta.limit}`}
class={meta.page === Math.ceil(meta.total / meta.limit) ? 'pointer-events-none' : ''}
>
<Button variant="outline" disabled={meta.page === Math.ceil(meta.total / meta.limit)}
>{$t('app.audit_log.next')}</Button
>
</a>
<div class="mt-8">
<Pagination.Root count={meta.total} perPage={meta.limit} page={meta.page}>
{#snippet children({ pages, currentPage })}
<Pagination.Content>
<Pagination.Item>
<a
href={`/dashboard/compliance/audit-log?page=${
currentPage - 1
}&limit=${meta.limit}`}
>
<Pagination.PrevButton>
<ChevronLeft class="h-4 w-4" />
<span class="hidden sm:block">{$t('app.audit_log.prev')}</span>
</Pagination.PrevButton>
</a>
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<a
href={`/dashboard/compliance/audit-log?page=${page.value}&limit=${meta.limit}`}
>
<Pagination.Link {page} isActive={currentPage === page.value}>
{page.value}
</Pagination.Link>
</a>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<a
href={`/dashboard/compliance/audit-log?page=${
currentPage + 1
}&limit=${meta.limit}`}
>
<Pagination.NextButton>
<span class="hidden sm:block">{$t('app.audit_log.next')}</span>
<ChevronRight class="h-4 w-4" />
</Pagination.NextButton>
</a>
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}

View File

@@ -17,6 +17,9 @@
import CircleAlertIcon from '@lucide/svelte/icons/circle-alert';
import * as Alert from '$lib/components/ui/alert/index.js';
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';
let { data }: { data: PageData } = $props();
let searchResult = $derived(data.searchResult);
@@ -121,55 +124,6 @@
return snippets;
}
const getPaginationItems = (currentPage: number, totalPages: number, siblingCount = 1) => {
const totalPageNumbers = siblingCount + 5;
if (totalPages <= totalPageNumbers) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages);
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex < totalPages - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPages;
if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingCount;
let leftRange = Array.from({ length: leftItemCount }, (_, i) => i + 1);
return [...leftRange, '...', totalPages];
}
if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 * siblingCount;
let rightRange = Array.from(
{ length: rightItemCount },
(_, i) => totalPages - rightItemCount + i + 1
);
return [firstPageIndex, '...', ...rightRange];
}
if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = Array.from(
{ length: rightSiblingIndex - leftSiblingIndex + 1 },
(_, i) => leftSiblingIndex + i
);
return [firstPageIndex, '...', ...middleRange, '...', lastPageIndex];
}
return [];
};
let paginationItems = $derived(
getPaginationItems(
page,
Math.ceil((searchResult?.total || 0) / (searchResult?.limit || 10))
)
);
</script>
<svelte:head>
@@ -336,42 +290,57 @@
</div>
{#if searchResult.total > searchResult.limit}
<div class="mt-8 flex flex-row items-center justify-center space-x-2">
<a
href={`/dashboard/search?keywords=${keywords}&page=${
page - 1
}&matchingStrategy=${matchingStrategy}`}
class={page === 1 ? 'pointer-events-none' : ''}
>
<Button variant="outline" disabled={page === 1}>{$t('app.search.prev')}</Button>
</a>
{#each paginationItems as item}
{#if typeof item === 'number'}
<a
href={`/dashboard/search?keywords=${keywords}&page=${item}&matchingStrategy=${matchingStrategy}`}
>
<Button variant={item === page ? 'default' : 'outline'}>{item}</Button>
</a>
{:else}
<span class="px-4 py-2">...</span>
{/if}
{/each}
<a
href={`/dashboard/search?keywords=${keywords}&page=${
page + 1
}&matchingStrategy=${matchingStrategy}`}
class={page === Math.ceil(searchResult.total / searchResult.limit)
? 'pointer-events-none'
: ''}
>
<Button
variant="outline"
disabled={page === Math.ceil(searchResult.total / searchResult.limit)}
>{$t('app.search.next')}</Button
>
</a>
<div class="mt-8">
<Pagination.Root count={searchResult.total} perPage={searchResult.limit} {page}>
{#snippet children({ pages, currentPage })}
<Pagination.Content>
<Pagination.Item>
<a
href={`/dashboard/search?keywords=${keywords}&page=${
currentPage - 1
}&matchingStrategy=${matchingStrategy}`}
>
<Pagination.PrevButton>
<ChevronLeft class="h-4 w-4" />
<span class="hidden sm:block">{$t('app.search.prev')}</span>
</Pagination.PrevButton>
</a>
</Pagination.Item>
{#each pages as page (page.key)}
{#if page.type === 'ellipsis'}
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
{:else}
<Pagination.Item>
<a
href={`/dashboard/search?keywords=${keywords}&page=${page.value}&matchingStrategy=${matchingStrategy}`}
>
<Pagination.Link
{page}
isActive={currentPage === page.value}
>
{page.value}
</Pagination.Link>
</a>
</Pagination.Item>
{/if}
{/each}
<Pagination.Item>
<a
href={`/dashboard/search?keywords=${keywords}&page=${
currentPage + 1
}&matchingStrategy=${matchingStrategy}`}
>
<Pagination.NextButton>
<span class="hidden sm:block">{$t('app.search.next')}</span>
<ChevronRight class="h-4 w-4" />
</Pagination.NextButton>
</a>
</Pagination.Item>
</Pagination.Content>
{/snippet}
</Pagination.Root>
</div>
{/if}
{/if}

146
pnpm-lock.yaml generated
View File

@@ -83,12 +83,6 @@ importers:
'@azure/msal-node':
specifier: ^3.6.3
version: 3.6.3
'@bull-board/api':
specifier: ^6.13.0
version: 6.13.0(@bull-board/ui@6.13.0)
'@bull-board/express':
specifier: ^6.13.0
version: 6.13.0
'@casl/ability':
specifier: ^6.7.3
version: 6.7.3
@@ -283,9 +277,6 @@ importers:
'@sveltejs/kit':
specifier: ^2.38.1
version: 2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1))
bits-ui:
specifier: ^2.8.10
version: 2.8.10(@internationalized/date@3.8.2)(svelte@5.35.5)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -324,8 +315,8 @@ importers:
specifier: ^3.8.2
version: 3.8.2
'@lucide/svelte':
specifier: ^0.515.0
version: 0.515.0(svelte@5.35.5)
specifier: ^0.544.0
version: 0.544.0(svelte@5.35.5)
'@sveltejs/adapter-auto':
specifier: ^6.0.0
version: 6.0.1(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))
@@ -344,6 +335,9 @@ importers:
'@types/semver':
specifier: ^7.7.1
version: 7.7.1
bits-ui:
specifier: ^2.12.0
version: 2.12.0(@internationalized/date@3.8.2)(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)
dotenv:
specifier: ^17.2.0
version: 17.2.0
@@ -652,17 +646,6 @@ packages:
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
engines: {node: '>=6.9.0'}
'@bull-board/api@6.13.0':
resolution: {integrity: sha512-GZ0On0VeL5uZVS1x7UdU90F9GV1kdmHa1955hW3Ow1PmslCY/2YwmvnapVdbvCUSVBqluTfbVZsE9X3h79r1kw==}
peerDependencies:
'@bull-board/ui': 6.13.0
'@bull-board/express@6.13.0':
resolution: {integrity: sha512-PAbzD3dplV2NtN8ETs00bp++pBOD+cVb1BEYltXrjyViA2WluDBVKdlh/2wM+sHbYO2TAMNg8bUtKxGNCmxG7w==}
'@bull-board/ui@6.13.0':
resolution: {integrity: sha512-63I6b3nZnKWI5ok6mw/Tk2rIObuzMTY/tLGyO51p0GW4rAImdXxrK6mT7j4SgEuP2B+tt/8L1jU7sLu8MMcCNw==}
'@casl/ability@6.7.3':
resolution: {integrity: sha512-A4L28Ko+phJAsTDhRjzCOZWECQWN2jzZnJPnROWWHjJpyMq1h7h9ZqjwS2WbIUa3Z474X1ZPSgW0f1PboZGC0A==}
@@ -1205,8 +1188,8 @@ packages:
'@layerstack/utils@2.0.0-next.12':
resolution: {integrity: sha512-fhGZUlSr3N+D44BYm37WKMGSEFyZBW+dwIqtGU8Cl54mR4TLQ/UwyGhdpgIHyH/x/8q1abE0fP0Dn6ZsrDE3BA==}
'@lucide/svelte@0.515.0':
resolution: {integrity: sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==}
'@lucide/svelte@0.544.0':
resolution: {integrity: sha512-9f9O6uxng2pLB01sxNySHduJN3HTl5p0HDu4H26VR51vhZfiMzyOMe9Mhof3XAk4l813eTtl+/DYRvGyoRR+yw==}
peerDependencies:
svelte: ^5
@@ -2176,8 +2159,8 @@ packages:
birpc@2.5.0:
resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==}
bits-ui@2.8.10:
resolution: {integrity: sha512-MOobkqapDZNrpcNmeL2g664xFmH4tZBOKBTxFmsQYMZQuybSZHQnPXy+AjM5XZEXRmCFx5+XRmo6+fC3vHh1hQ==}
bits-ui@2.12.0:
resolution: {integrity: sha512-8NF4ILNyAJlIxDXpl/akGXGBV5QmZAe+8gTfPttM5P6/+LrijumcSfFXY5cr4QkXwTmLA7H5stYpbgJf2XFJvg==}
engines: {node: '>=20'}
peerDependencies:
'@internationalized/date': ^3.8.1
@@ -2726,11 +2709,6 @@ packages:
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
ejs@3.1.10:
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
engines: {node: '>=0.10.0'}
hasBin: true
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@@ -2886,9 +2864,6 @@ packages:
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -3217,11 +3192,6 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'}
hasBin: true
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
@@ -3416,6 +3386,10 @@ packages:
resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==}
engines: {node: '>=12'}
lz-string@1.5.0:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -4017,9 +3991,6 @@ packages:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
redis-info@3.1.0:
resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
@@ -4092,10 +4063,14 @@ packages:
peerDependencies:
svelte: ^5.7.0
runed@0.29.2:
resolution: {integrity: sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==}
runed@0.35.1:
resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
peerDependencies:
'@sveltejs/kit': ^2.21.0
svelte: ^5.7.0
peerDependenciesMeta:
'@sveltejs/kit':
optional: true
rw@1.3.3:
resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
@@ -4351,18 +4326,18 @@ packages:
peerDependencies:
svelte: ^5.0.0
svelte-toolbelt@0.10.6:
resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
peerDependencies:
svelte: ^5.30.2
svelte-toolbelt@0.7.1:
resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
peerDependencies:
svelte: ^5.0.0
svelte-toolbelt@0.9.3:
resolution: {integrity: sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
peerDependencies:
svelte: ^5.30.2
svelte@5.35.5:
resolution: {integrity: sha512-KuRvI82rhh0RMz1EKsUJD96gZyHJ+h2+8zrwO8iqE/p/CmcNKvIItDUAeUePhuCDgtegDJmF8IKThbHIfmTgTA==}
engines: {node: '>=18'}
@@ -5367,24 +5342,6 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@bull-board/api@6.13.0(@bull-board/ui@6.13.0)':
dependencies:
'@bull-board/ui': 6.13.0
redis-info: 3.1.0
'@bull-board/express@6.13.0':
dependencies:
'@bull-board/api': 6.13.0(@bull-board/ui@6.13.0)
'@bull-board/ui': 6.13.0
ejs: 3.1.10
express: 5.1.0
transitivePeerDependencies:
- supports-color
'@bull-board/ui@6.13.0':
dependencies:
'@bull-board/api': 6.13.0(@bull-board/ui@6.13.0)
'@casl/ability@6.7.3':
dependencies:
'@ucast/mongo2js': 1.4.0
@@ -5738,7 +5695,7 @@ snapshots:
d3-time-format: 4.1.0
lodash-es: 4.17.21
'@lucide/svelte@0.515.0(svelte@5.35.5)':
'@lucide/svelte@0.544.0(svelte@5.35.5)':
dependencies:
svelte: 5.35.5
@@ -6805,16 +6762,18 @@ snapshots:
birpc@2.5.0: {}
bits-ui@2.8.10(@internationalized/date@3.8.2)(svelte@5.35.5):
bits-ui@2.12.0(@internationalized/date@3.8.2)(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5):
dependencies:
'@floating-ui/core': 1.7.2
'@floating-ui/dom': 1.7.2
'@internationalized/date': 3.8.2
esm-env: 1.2.2
runed: 0.29.2(svelte@5.35.5)
runed: 0.35.1(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)
svelte: 5.35.5
svelte-toolbelt: 0.9.3(svelte@5.35.5)
svelte-toolbelt: 0.10.6(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)
tabbable: 6.2.0
transitivePeerDependencies:
- '@sveltejs/kit'
bl@4.1.0:
dependencies:
@@ -7293,10 +7252,6 @@ snapshots:
ee-first@1.1.1: {}
ejs@3.1.10:
dependencies:
jake: 10.9.2
emoji-regex-xs@1.0.0: {}
emoji-regex@8.0.0: {}
@@ -7518,10 +7473,6 @@ snapshots:
file-uri-to-path@1.0.0: {}
filelist@1.0.4:
dependencies:
minimatch: 5.1.6
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -7917,13 +7868,6 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jake@10.9.2:
dependencies:
async: 3.2.6
chalk: 4.1.2
filelist: 1.0.4
minimatch: 3.1.2
jiti@2.4.2: {}
jose@6.0.11: {}
@@ -8128,6 +8072,8 @@ snapshots:
luxon@3.7.1: {}
lz-string@1.5.0: {}
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.4
@@ -8717,10 +8663,6 @@ snapshots:
redis-errors@1.2.0: {}
redis-info@3.1.0:
dependencies:
lodash: 4.17.21
redis-parser@3.0.0:
dependencies:
redis-errors: 1.2.0
@@ -8814,10 +8756,14 @@ snapshots:
esm-env: 1.2.2
svelte: 5.35.5
runed@0.29.2(svelte@5.35.5):
runed@0.35.1(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5):
dependencies:
dequal: 2.0.3
esm-env: 1.2.2
lz-string: 1.5.0
svelte: 5.35.5
optionalDependencies:
'@sveltejs/kit': 2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1))
rw@1.3.3: {}
@@ -9103,6 +9049,15 @@ snapshots:
runed: 0.28.0(svelte@5.35.5)
svelte: 5.35.5
svelte-toolbelt@0.10.6(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5):
dependencies:
clsx: 2.1.1
runed: 0.35.1(@sveltejs/kit@2.38.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)(vite@6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.35.5)
style-to-object: 1.0.9
svelte: 5.35.5
transitivePeerDependencies:
- '@sveltejs/kit'
svelte-toolbelt@0.7.1(svelte@5.35.5):
dependencies:
clsx: 2.1.1
@@ -9110,13 +9065,6 @@ snapshots:
style-to-object: 1.0.9
svelte: 5.35.5
svelte-toolbelt@0.9.3(svelte@5.35.5):
dependencies:
clsx: 2.1.1
runed: 0.29.2(svelte@5.35.5)
style-to-object: 1.0.9
svelte: 5.35.5
svelte@5.35.5:
dependencies:
'@ampproject/remapping': 2.3.0