diff --git a/README.md b/README.md index 0518088..cc47d18 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ settings included, belongs to Stalwart, so the container is disposable. | | | | --- | --- | -| **Inbox & conversation (dark)** ![Inbox, dark theme](docs/screenshots/inbox-dark.jpg) | **Inbox & conversation (light)** ![Inbox, light theme](docs/screenshots/inbox-light.jpg) | -| **Composer** ![Composer](docs/screenshots/compose.jpg) | **Calendar** ![Calendar](docs/screenshots/calendar.jpg) | -| **Contacts** ![Contacts](docs/screenshots/contacts.jpg) | **Sieve filter builder** ![Filters](docs/screenshots/filters.jpg) | +| **Inbox & conversation (dark)** ![Inbox, dark theme](screenshots/inbox-dark.jpg) | **Inbox & conversation (light)** ![Inbox, light theme](screenshots/inbox-light.jpg) | +| **Composer** ![Composer](screenshots/compose.jpg) | **Calendar** ![Calendar](screenshots/calendar.jpg) | +| **Contacts** ![Contacts](screenshots/contacts.jpg) | **Sieve filter builder** ![Filters](screenshots/filters.jpg) | Taken against the built-in mock with sample data. More, including the phone layout, on [ihasmail.org](https://ihasmail.org/#screenshots). diff --git a/docs/screenshots/calendar.jpg b/screenshots/calendar.jpg similarity index 100% rename from docs/screenshots/calendar.jpg rename to screenshots/calendar.jpg diff --git a/docs/screenshots/compose.jpg b/screenshots/compose.jpg similarity index 100% rename from docs/screenshots/compose.jpg rename to screenshots/compose.jpg diff --git a/docs/screenshots/contacts.jpg b/screenshots/contacts.jpg similarity index 100% rename from docs/screenshots/contacts.jpg rename to screenshots/contacts.jpg diff --git a/docs/screenshots/files.jpg b/screenshots/files.jpg similarity index 100% rename from docs/screenshots/files.jpg rename to screenshots/files.jpg diff --git a/docs/screenshots/filters.jpg b/screenshots/filters.jpg similarity index 100% rename from docs/screenshots/filters.jpg rename to screenshots/filters.jpg diff --git a/docs/screenshots/inbox-dark.jpg b/screenshots/inbox-dark.jpg similarity index 100% rename from docs/screenshots/inbox-dark.jpg rename to screenshots/inbox-dark.jpg diff --git a/docs/screenshots/inbox-light.jpg b/screenshots/inbox-light.jpg similarity index 100% rename from docs/screenshots/inbox-light.jpg rename to screenshots/inbox-light.jpg diff --git a/docs/screenshots/login.jpg b/screenshots/login.jpg similarity index 100% rename from docs/screenshots/login.jpg rename to screenshots/login.jpg diff --git a/docs/screenshots/mobile.jpg b/screenshots/mobile.jpg similarity index 100% rename from docs/screenshots/mobile.jpg rename to screenshots/mobile.jpg diff --git a/docs/screenshots/recipients.jpg b/screenshots/recipients.jpg similarity index 100% rename from docs/screenshots/recipients.jpg rename to screenshots/recipients.jpg diff --git a/docs/screenshots-light.mjs b/scripts/screenshots-light.mjs similarity index 100% rename from docs/screenshots-light.mjs rename to scripts/screenshots-light.mjs diff --git a/docs/screenshots.mjs b/scripts/screenshots.mjs similarity index 98% rename from docs/screenshots.mjs rename to scripts/screenshots.mjs index b828488..d34b7e9 100644 --- a/docs/screenshots.mjs +++ b/scripts/screenshots.mjs @@ -5,8 +5,8 @@ * images already use rather than whatever a window happens to be. * * npm run dev:mock # in another terminal - * node docs/screenshots.mjs docs/screenshots - * node docs/screenshots-light.mjs docs/screenshots + * node scripts/screenshots.mjs screenshots + * node scripts/screenshots-light.mjs screenshots * * Restart the mock before a run. The filters shot creates rules, so a second * run against the same mock shows them twice. @@ -30,7 +30,7 @@ * flip --bg to #f6f8fa. The compositor simply does not repaint everything a * CSS-variable change touches while metrics are overridden. Launching Chrome * at --window-size and never calling setDeviceMetricsOverride renders it - * correctly, which is what docs/screenshots-light.mjs does. + * correctly, which is what scripts/screenshots-light.mjs does. * * assertTheme() stays either way: without it this script wrote a dark * screenshot under a light caption and reported success, and that is how the @@ -226,7 +226,7 @@ try { await evaluate(`(() => { const c = [...document.querySelectorAll('button')].find(b => /close|discard/i.test(b.getAttribute('aria-label')||'')); if (c) c.click(); })()`); await sleep(800); - // (inbox-light is captured by docs/screenshots-light.mjs -- see the header) + // (inbox-light is captured by scripts/screenshots-light.mjs -- see the header) // --- calendar --- diff --git a/web/src/lib/__tests__/describeRules.test.ts b/web/src/lib/__tests__/describeRules.test.ts index c14d51a..a09d79f 100644 --- a/web/src/lib/__tests__/describeRules.test.ts +++ b/web/src/lib/__tests__/describeRules.test.ts @@ -7,7 +7,7 @@ */ import { describe, expect, it } from "vitest"; import { describeRule as describeSieve } from "../sieve"; -import { describeRule as describeRecurrence, weekdayOptions } from "../recurrence"; +import { describeRule as describeRecurrence, weekdayOptions } from "../calendar/recurrence"; import { setUiLanguageForFormatting } from "../datetime"; import { setCatalog } from "../i18n"; diff --git a/web/src/lib/__tests__/adminAccess.test.ts b/web/src/lib/admin/__tests__/adminAccess.test.ts similarity index 98% rename from web/src/lib/__tests__/adminAccess.test.ts rename to web/src/lib/admin/__tests__/adminAccess.test.ts index a767835..ac774e9 100644 --- a/web/src/lib/__tests__/adminAccess.test.ts +++ b/web/src/lib/admin/__tests__/adminAccess.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/adminAccess"; +import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/admin/adminAccess"; const set = (...p: string[]) => permissionSet(p); const everything = set(...ADMIN_BASELINE, "sysTenantGet", "jmapEmailGet", "impersonate"); diff --git a/web/src/lib/__tests__/adminDashboard.test.ts b/web/src/lib/admin/__tests__/adminDashboard.test.ts similarity index 98% rename from web/src/lib/__tests__/adminDashboard.test.ts rename to web/src/lib/admin/__tests__/adminDashboard.test.ts index 8aef9d4..e33be5a 100644 --- a/web/src/lib/__tests__/adminDashboard.test.ts +++ b/web/src/lib/admin/__tests__/adminDashboard.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { client, JmapMethodError } from "@/jmap/client"; -import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/adminDashboard"; +import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/admin/adminDashboard"; const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp }); diff --git a/web/src/lib/__tests__/adminDirectory.test.ts b/web/src/lib/admin/__tests__/adminDirectory.test.ts similarity index 99% rename from web/src/lib/__tests__/adminDirectory.test.ts rename to web/src/lib/admin/__tests__/adminDirectory.test.ts index 25f7ad1..87932c0 100644 --- a/web/src/lib/__tests__/adminDirectory.test.ts +++ b/web/src/lib/admin/__tests__/adminDirectory.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { client } from "@/jmap/client"; -import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/adminDirectory"; +import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/admin/adminDirectory"; describe("setting a password", () => { it("writes into the existing password credential, keeping its place", () => { diff --git a/web/src/lib/__tests__/adminDomains.test.ts b/web/src/lib/admin/__tests__/adminDomains.test.ts similarity index 98% rename from web/src/lib/__tests__/adminDomains.test.ts rename to web/src/lib/admin/__tests__/adminDomains.test.ts index 4b0cb03..01d132b 100644 --- a/web/src/lib/__tests__/adminDomains.test.ts +++ b/web/src/lib/admin/__tests__/adminDomains.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains"; +import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/admin/adminDomains"; /** * Written the way Stalwart's BIND serializer writes it (dns-update's diff --git a/web/src/lib/__tests__/adminGroups.test.ts b/web/src/lib/admin/__tests__/adminGroups.test.ts similarity index 98% rename from web/src/lib/__tests__/adminGroups.test.ts rename to web/src/lib/admin/__tests__/adminGroups.test.ts index 04de7fb..24912b1 100644 --- a/web/src/lib/__tests__/adminGroups.test.ts +++ b/web/src/lib/admin/__tests__/adminGroups.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { client } from "@/jmap/client"; -import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/adminGroups"; +import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/admin/adminGroups"; describe("group membership", () => { it("is a patch to each member, one pointer each, so no other membership moves", () => { diff --git a/web/src/lib/__tests__/adminLists.test.ts b/web/src/lib/admin/__tests__/adminLists.test.ts similarity index 98% rename from web/src/lib/__tests__/adminLists.test.ts rename to web/src/lib/admin/__tests__/adminLists.test.ts index 4828864..c082a6b 100644 --- a/web/src/lib/__tests__/adminLists.test.ts +++ b/web/src/lib/admin/__tests__/adminLists.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { client } from "@/jmap/client"; -import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/adminLists"; +import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/admin/adminLists"; describe("a mailing list's recipients", () => { it("are saved as what was added and removed, one pointer each", () => { diff --git a/web/src/lib/__tests__/adminRoles.test.ts b/web/src/lib/admin/__tests__/adminRoles.test.ts similarity index 94% rename from web/src/lib/__tests__/adminRoles.test.ts rename to web/src/lib/admin/__tests__/adminRoles.test.ts index b2008e8..9852011 100644 --- a/web/src/lib/__tests__/adminRoles.test.ts +++ b/web/src/lib/admin/__tests__/adminRoles.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { permissionSet } from "@/lib/adminAccess"; -import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/adminRoles"; +import { permissionSet } from "@/lib/admin/adminAccess"; +import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/admin/adminRoles"; const flags = (...n: string[]) => Object.fromEntries(n.map((x) => [x, true])); const roles = new Map([ diff --git a/web/src/lib/__tests__/adminTenants.test.ts b/web/src/lib/admin/__tests__/adminTenants.test.ts similarity index 98% rename from web/src/lib/__tests__/adminTenants.test.ts rename to web/src/lib/admin/__tests__/adminTenants.test.ts index b865855..ea23ef1 100644 --- a/web/src/lib/__tests__/adminTenants.test.ts +++ b/web/src/lib/admin/__tests__/adminTenants.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { client } from "@/jmap/client"; -import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/adminTenants"; +import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/admin/adminTenants"; describe("a tenant's limits", () => { it("change one pointer each, leaving the quotas ihasmail does not offer alone", () => { diff --git a/web/src/lib/adminAccess.ts b/web/src/lib/admin/adminAccess.ts similarity index 100% rename from web/src/lib/adminAccess.ts rename to web/src/lib/admin/adminAccess.ts diff --git a/web/src/lib/adminDashboard.ts b/web/src/lib/admin/adminDashboard.ts similarity index 100% rename from web/src/lib/adminDashboard.ts rename to web/src/lib/admin/adminDashboard.ts diff --git a/web/src/lib/adminDirectory.ts b/web/src/lib/admin/adminDirectory.ts similarity index 99% rename from web/src/lib/adminDirectory.ts rename to web/src/lib/admin/adminDirectory.ts index 10d00ba..85781d6 100644 --- a/web/src/lib/adminDirectory.ts +++ b/web/src/lib/admin/adminDirectory.ts @@ -1,6 +1,6 @@ import { client } from "@/jmap/client"; import { t } from "@/lib/i18n"; -import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/adminAccess"; +import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/admin/adminAccess"; /** * Stalwart 0.16's directory, over the ordinary JMAP proxy. diff --git a/web/src/lib/adminDomains.ts b/web/src/lib/admin/adminDomains.ts similarity index 99% rename from web/src/lib/adminDomains.ts rename to web/src/lib/admin/adminDomains.ts index ea4e817..38d1903 100644 --- a/web/src/lib/adminDomains.ts +++ b/web/src/lib/admin/adminDomains.ts @@ -1,6 +1,6 @@ import { client } from "@/jmap/client"; import { plural, t } from "@/lib/i18n"; -import { DirectoryError } from "@/lib/adminDirectory"; +import { DirectoryError } from "@/lib/admin/adminDirectory"; /** * Stalwart 0.16's domains, over the same proxy as accounts. diff --git a/web/src/lib/adminGroups.ts b/web/src/lib/admin/adminGroups.ts similarity index 98% rename from web/src/lib/adminGroups.ts rename to web/src/lib/admin/adminGroups.ts index 1f75a5a..c9ed7c4 100644 --- a/web/src/lib/adminGroups.ts +++ b/web/src/lib/admin/adminGroups.ts @@ -1,7 +1,7 @@ import { client } from "@/jmap/client"; import { t } from "@/lib/i18n"; -import type { PermissionsMode, UserRoles } from "@/lib/adminAccess"; -import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/adminDirectory"; +import type { PermissionsMode, UserRoles } from "@/lib/admin/adminAccess"; +import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/admin/adminDirectory"; /** * Groups, from Stalwart 0.16's directory. diff --git a/web/src/lib/adminLists.ts b/web/src/lib/admin/adminLists.ts similarity index 98% rename from web/src/lib/adminLists.ts rename to web/src/lib/admin/adminLists.ts index de7d1fc..d3e42c1 100644 --- a/web/src/lib/adminLists.ts +++ b/web/src/lib/admin/adminLists.ts @@ -1,6 +1,6 @@ import { client } from "@/jmap/client"; import { t } from "@/lib/i18n"; -import { DirectoryError, type EmailAlias } from "@/lib/adminDirectory"; +import { DirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory"; /** * Mailing lists, from Stalwart 0.16's directory. diff --git a/web/src/lib/adminRoles.ts b/web/src/lib/admin/adminRoles.ts similarity index 97% rename from web/src/lib/adminRoles.ts rename to web/src/lib/admin/adminRoles.ts index 2f47b63..9462b6b 100644 --- a/web/src/lib/adminRoles.ts +++ b/web/src/lib/admin/adminRoles.ts @@ -1,8 +1,8 @@ import { apiFetch, client } from "@/jmap/client"; import { t } from "@/lib/i18n"; -import type { Permissions, RoleDef } from "@/lib/adminAccess"; -import { DirectoryError } from "@/lib/adminDirectory"; -import { DomainError } from "@/lib/adminDomains"; +import type { Permissions, RoleDef } from "@/lib/admin/adminAccess"; +import { DirectoryError } from "@/lib/admin/adminDirectory"; +import { DomainError } from "@/lib/admin/adminDomains"; import type { PermissionInfo } from "@/lib/permissionLabels"; /** diff --git a/web/src/lib/adminTenants.ts b/web/src/lib/admin/adminTenants.ts similarity index 98% rename from web/src/lib/adminTenants.ts rename to web/src/lib/admin/adminTenants.ts index 31a2a1c..ade253f 100644 --- a/web/src/lib/adminTenants.ts +++ b/web/src/lib/admin/adminTenants.ts @@ -1,7 +1,7 @@ import { client } from "@/jmap/client"; import { t } from "@/lib/i18n"; -import { DirectoryError } from "@/lib/adminDirectory"; -import { DomainError } from "@/lib/adminDomains"; +import { DirectoryError } from "@/lib/admin/adminDirectory"; +import { DomainError } from "@/lib/admin/adminDomains"; /** * Tenants, from Stalwart 0.16's directory. diff --git a/web/src/lib/__tests__/appointment.test.ts b/web/src/lib/calendar/__tests__/appointment.test.ts similarity index 98% rename from web/src/lib/__tests__/appointment.test.ts rename to web/src/lib/calendar/__tests__/appointment.test.ts index 1cd582a..35fb42c 100644 --- a/web/src/lib/__tests__/appointment.test.ts +++ b/web/src/lib/calendar/__tests__/appointment.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { appointmentDraft, nextHalfHour } from "@/lib/appointment"; +import { appointmentDraft, nextHalfHour } from "@/lib/calendar/appointment"; import type { Email, EmailBodyPart } from "@/jmap/types"; /** diff --git a/web/src/lib/__tests__/availabilityWindow.test.ts b/web/src/lib/calendar/__tests__/availabilityWindow.test.ts similarity index 98% rename from web/src/lib/__tests__/availabilityWindow.test.ts rename to web/src/lib/calendar/__tests__/availabilityWindow.test.ts index 107af46..355e4b7 100644 --- a/web/src/lib/__tests__/availabilityWindow.test.ts +++ b/web/src/lib/calendar/__tests__/availabilityWindow.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { availabilityWindow } from "@/lib/availabilityWindow"; +import { availabilityWindow } from "@/lib/calendar/availabilityWindow"; const at = (s: string) => new Date(s); const hours = (w: { ticks: { time: Date }[] }) => w.ticks.map((t) => `${t.time.getDate()}@${t.time.getHours()}`); diff --git a/web/src/lib/__tests__/eventDrag.test.ts b/web/src/lib/calendar/__tests__/eventDrag.test.ts similarity index 99% rename from web/src/lib/__tests__/eventDrag.test.ts rename to web/src/lib/calendar/__tests__/eventDrag.test.ts index af21360..d8efd5b 100644 --- a/web/src/lib/__tests__/eventDrag.test.ts +++ b/web/src/lib/calendar/__tests__/eventDrag.test.ts @@ -13,7 +13,7 @@ import { dayDelta, resizePatch, SNAP_MINUTES, -} from "@/lib/eventDrag"; +} from "@/lib/calendar/eventDrag"; import { BIRTHDAY_ID_PREFIX } from "@/lib/birthdays"; import type { CalendarEvent } from "@/jmap/types"; diff --git a/web/src/lib/__tests__/ics.test.ts b/web/src/lib/calendar/__tests__/ics.test.ts similarity index 99% rename from web/src/lib/__tests__/ics.test.ts rename to web/src/lib/calendar/__tests__/ics.test.ts index 5e0961a..70f3cce 100644 --- a/web/src/lib/__tests__/ics.test.ts +++ b/web/src/lib/calendar/__tests__/ics.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/ics"; +import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/calendar/ics"; const cal = (body: string) => `BEGIN:VCALENDAR\r\nVERSION:2.0\r\n${body}\r\nEND:VCALENDAR\r\n`; const event = (props: string) => `BEGIN:VEVENT\r\n${props}\r\nEND:VEVENT`; diff --git a/web/src/lib/__tests__/icsWrite.test.ts b/web/src/lib/calendar/__tests__/icsWrite.test.ts similarity index 99% rename from web/src/lib/__tests__/icsWrite.test.ts rename to web/src/lib/calendar/__tests__/icsWrite.test.ts index b342dc4..1802299 100644 --- a/web/src/lib/__tests__/icsWrite.test.ts +++ b/web/src/lib/calendar/__tests__/icsWrite.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { toIcs, parseIcs } from "@/lib/ics"; +import { toIcs, parseIcs } from "@/lib/calendar/ics"; import type { JSCalendarEvent } from "@/jmap/types"; /* diff --git a/web/src/lib/appointment.ts b/web/src/lib/calendar/appointment.ts similarity index 96% rename from web/src/lib/appointment.ts rename to web/src/lib/calendar/appointment.ts index e5bfffc..5162eee 100644 --- a/web/src/lib/appointment.ts +++ b/web/src/lib/calendar/appointment.ts @@ -1,9 +1,9 @@ import type { Email, EmailAddress } from "@/jmap/types"; import { useCalendar, type EventDraft } from "@/store/calendar"; import { useMail } from "@/store/mail"; -import { uniqueAddresses } from "./address"; -import { toLocalDateOnly } from "./dates"; -import { htmlToText } from "./text"; +import { uniqueAddresses } from "../address"; +import { toLocalDateOnly } from "../dates"; +import { htmlToText } from "../text"; /** * How much of a message body is copied into an event description. diff --git a/web/src/lib/availabilityWindow.ts b/web/src/lib/calendar/availabilityWindow.ts similarity index 100% rename from web/src/lib/availabilityWindow.ts rename to web/src/lib/calendar/availabilityWindow.ts diff --git a/web/src/lib/eventDrag.ts b/web/src/lib/calendar/eventDrag.ts similarity index 98% rename from web/src/lib/eventDrag.ts rename to web/src/lib/calendar/eventDrag.ts index 667937f..54a77f8 100644 --- a/web/src/lib/eventDrag.ts +++ b/web/src/lib/calendar/eventDrag.ts @@ -10,8 +10,8 @@ * question — given an event and a gesture, what are the new start and end — * and the caller decides whether it is allowed to save that. */ -import { addMinutes } from "./dates"; -import { isBirthdayEvent } from "./birthdays"; +import { addMinutes } from "../dates"; +import { isBirthdayEvent } from "../birthdays"; import type { CalendarEvent } from "@/jmap/types"; /** diff --git a/web/src/lib/ics.ts b/web/src/lib/calendar/ics.ts similarity index 100% rename from web/src/lib/ics.ts rename to web/src/lib/calendar/ics.ts diff --git a/web/src/lib/recurrence.ts b/web/src/lib/calendar/recurrence.ts similarity index 99% rename from web/src/lib/recurrence.ts rename to web/src/lib/calendar/recurrence.ts index b90e01f..2b945b1 100644 --- a/web/src/lib/recurrence.ts +++ b/web/src/lib/calendar/recurrence.ts @@ -1,5 +1,5 @@ import type { JSCalendarRecurrenceRule, JSCalendarNDay } from "@/jmap/types"; -import { formatList, weekdayName, weekdayNames } from "./datetime"; +import { formatList, weekdayName, weekdayNames } from "../datetime"; import { plural, t } from "@/lib/i18n"; /** diff --git a/web/src/store/calendar.ts b/web/src/store/calendar.ts index ee402c2..1cbb5ec 100644 --- a/web/src/store/calendar.ts +++ b/web/src/store/calendar.ts @@ -5,7 +5,7 @@ import { toUTCDate, toLocalDateTime, zonedToDate, parseDuration, DAY_MS, browser import { t } from "@/lib/i18n"; import { useContacts } from "./contacts"; import { BIRTHDAY_CALENDAR_ID, birthdaysInRange, isBirthdayEvent, type Birthday } from "@/lib/birthdays"; -import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/ics"; +import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/calendar/ics"; import { withBase } from "@/lib/basePath"; import { settings, useSettings } from "./settings"; import { useSession } from "./session"; diff --git a/web/src/views/AppShell.tsx b/web/src/views/AppShell.tsx index bf6f80b..f716ad9 100644 --- a/web/src/views/AppShell.tsx +++ b/web/src/views/AppShell.tsx @@ -22,7 +22,7 @@ import { formatSize } from "@/lib/format"; import { collectShare } from "@/lib/shareTarget"; import { TranslateBoundary } from "@/ui/TranslateBoundary"; import { t } from "@/lib/i18n"; -import { hasAdministration } from "@/lib/adminAccess"; +import { hasAdministration } from "@/lib/admin/adminAccess"; import { usePermissions } from "./admin/usePermissions"; import { AdminNav } from "./admin/AdminNav"; diff --git a/web/src/views/admin/AccountSheet.tsx b/web/src/views/admin/AccountSheet.tsx index 6946fc9..8cdb3fb 100644 --- a/web/src/views/admin/AccountSheet.tsx +++ b/web/src/views/admin/AccountSheet.tsx @@ -7,7 +7,7 @@ import { generatePassword, outranks, type UserRoles, -} from "@/lib/adminAccess"; +} from "@/lib/admin/adminAccess"; import { aliasList, createAccount, @@ -20,7 +20,7 @@ import { DISK_QUOTA, type DirectoryAccount, type EmailAlias, -} from "@/lib/adminDirectory"; +} from "@/lib/admin/adminDirectory"; import { formatSize } from "@/lib/format"; import { t, tNode } from "@/lib/i18n"; import { Link } from "wouter"; diff --git a/web/src/views/admin/AccountsAdmin.tsx b/web/src/views/admin/AccountsAdmin.tsx index ec9d121..ebb3661 100644 --- a/web/src/views/admin/AccountsAdmin.tsx +++ b/web/src/views/admin/AccountsAdmin.tsx @@ -3,7 +3,7 @@ import { useLocation } from "wouter"; import { ChevronLeft, ChevronRight, Search, UserPlus, Users } from "lucide-react"; import { useSession } from "@/store/session"; import { STALWART_CAP } from "@/jmap/client"; -import { can, type RoleDef } from "@/lib/adminAccess"; +import { can, type RoleDef } from "@/lib/admin/adminAccess"; import { describeDirectoryError, getAccounts, @@ -14,14 +14,14 @@ import { DISK_QUOTA, type DirectoryAccount, type DirectoryDomain, -} from "@/lib/adminDirectory"; +} from "@/lib/admin/adminDirectory"; import { formatSize } from "@/lib/format"; import { plural, t } from "@/lib/i18n"; import { Avatar, Empty, Spinner } from "@/ui/misc"; import { usePermissions } from "./usePermissions"; import { isSelf, roleName, type DirectoryContext } from "./directoryContext"; import { AccountSheet } from "./AccountSheet"; -import { listTenantNames } from "@/lib/adminTenants"; +import { listTenantNames } from "@/lib/admin/adminTenants"; const PAGE_SIZE = 50; diff --git a/web/src/views/admin/AdminDashboard.tsx b/web/src/views/admin/AdminDashboard.tsx index 4483f51..8f7b7c4 100644 --- a/web/src/views/admin/AdminDashboard.tsx +++ b/web/src/views/admin/AdminDashboard.tsx @@ -1,8 +1,8 @@ import { useEffect, useState, type ReactNode } from "react"; import { Link } from "wouter"; import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react"; -import { adminSections, dashboardCards, type DashboardCard } from "@/lib/adminAccess"; -import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/adminDashboard"; +import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess"; +import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard"; import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime"; import { formatSize } from "@/lib/format"; import { t } from "@/lib/i18n"; diff --git a/web/src/views/admin/AdminNav.tsx b/web/src/views/admin/AdminNav.tsx index ab15c2c..fc7b1d3 100644 --- a/web/src/views/admin/AdminNav.tsx +++ b/web/src/views/admin/AdminNav.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import { Link, useLocation } from "wouter"; import { Building2, Globe, LayoutDashboard, List, ShieldCheck, User, UsersRound } from "lucide-react"; -import { adminSections, type AdminSection } from "@/lib/adminAccess"; +import { adminSections, type AdminSection } from "@/lib/admin/adminAccess"; import { t } from "@/lib/i18n"; import { usePermissions } from "./usePermissions"; diff --git a/web/src/views/admin/AdminView.tsx b/web/src/views/admin/AdminView.tsx index e8cbe43..36ca912 100644 --- a/web/src/views/admin/AdminView.tsx +++ b/web/src/views/admin/AdminView.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; import { Redirect } from "wouter"; -import { adminSections, type AdminSection } from "@/lib/adminAccess"; +import { adminSections, type AdminSection } from "@/lib/admin/adminAccess"; import { AccountsAdmin } from "./AccountsAdmin"; import { AdminDashboard } from "./AdminDashboard"; import { DomainsAdmin } from "./DomainsAdmin"; diff --git a/web/src/views/admin/DomainSheet.tsx b/web/src/views/admin/DomainSheet.tsx index 59f5d4d..b2c08cd 100644 --- a/web/src/views/admin/DomainSheet.tsx +++ b/web/src/views/admin/DomainSheet.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { Copy, Globe, Plus, Trash2, X } from "lucide-react"; -import { can } from "@/lib/adminAccess"; -import { describeDirectoryError } from "@/lib/adminDirectory"; +import { can } from "@/lib/admin/adminAccess"; +import { describeDirectoryError } from "@/lib/admin/adminDirectory"; import { createDomain, describeLinked, @@ -18,7 +18,7 @@ import { type DirectoryDomainFull, type DkimKey, type Managed, -} from "@/lib/adminDomains"; +} from "@/lib/admin/adminDomains"; import { formatFullDate } from "@/lib/format"; import { plural, t } from "@/lib/i18n"; import { Dialog } from "@/ui/dialog"; diff --git a/web/src/views/admin/DomainsAdmin.tsx b/web/src/views/admin/DomainsAdmin.tsx index d025d12..2128610 100644 --- a/web/src/views/admin/DomainsAdmin.tsx +++ b/web/src/views/admin/DomainsAdmin.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; import { useLocation } from "wouter"; import { ChevronLeft, ChevronRight, Globe, Plus, Search } from "lucide-react"; -import { can } from "@/lib/adminAccess"; -import { describeDirectoryError } from "@/lib/adminDirectory"; -import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/adminDomains"; +import { can } from "@/lib/admin/adminAccess"; +import { describeDirectoryError } from "@/lib/admin/adminDirectory"; +import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/admin/adminDomains"; import { plural, t } from "@/lib/i18n"; import { Empty, Spinner } from "@/ui/misc"; import { usePermissions } from "./usePermissions"; diff --git a/web/src/views/admin/GroupSheet.tsx b/web/src/views/admin/GroupSheet.tsx index 80cabe4..b8929d0 100644 --- a/web/src/views/admin/GroupSheet.tsx +++ b/web/src/views/admin/GroupSheet.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { Search, Trash2, UserMinus, UserPlus, X } from "lucide-react"; -import { can, canGrantRole } from "@/lib/adminAccess"; -import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/adminDirectory"; +import { can, canGrantRole } from "@/lib/admin/adminAccess"; +import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/admin/adminDirectory"; import { createGroup, destroyGroup, @@ -12,7 +12,7 @@ import { setMembership, type DirectoryGroup, type GroupMember, -} from "@/lib/adminGroups"; +} from "@/lib/admin/adminGroups"; import { formatSize } from "@/lib/format"; import { plural, t } from "@/lib/i18n"; import { Avatar, Spinner } from "@/ui/misc"; diff --git a/web/src/views/admin/GroupsAdmin.tsx b/web/src/views/admin/GroupsAdmin.tsx index e84e094..f195f25 100644 --- a/web/src/views/admin/GroupsAdmin.tsx +++ b/web/src/views/admin/GroupsAdmin.tsx @@ -3,9 +3,9 @@ import { useLocation } from "wouter"; import { ChevronLeft, ChevronRight, Plus, Search, UsersRound } from "lucide-react"; import { useSession } from "@/store/session"; import { STALWART_CAP } from "@/jmap/client"; -import { can, type RoleDef } from "@/lib/adminAccess"; -import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/adminDirectory"; -import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/adminGroups"; +import { can, type RoleDef } from "@/lib/admin/adminAccess"; +import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/admin/adminDirectory"; +import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/admin/adminGroups"; import { plural, t } from "@/lib/i18n"; import { Avatar, Empty, Spinner } from "@/ui/misc"; import { usePermissions } from "./usePermissions"; diff --git a/web/src/views/admin/ListSheet.tsx b/web/src/views/admin/ListSheet.tsx index 557393a..3326d67 100644 --- a/web/src/views/admin/ListSheet.tsx +++ b/web/src/views/admin/ListSheet.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from "react"; import { Plus, Search, Trash2, X } from "lucide-react"; -import { can } from "@/lib/adminAccess"; -import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/adminDirectory"; -import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/adminLists"; +import { can } from "@/lib/admin/adminAccess"; +import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory"; +import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/admin/adminLists"; import { plural, t } from "@/lib/i18n"; import { Dialog } from "@/ui/dialog"; import { toast } from "@/ui/toast"; diff --git a/web/src/views/admin/ListsAdmin.tsx b/web/src/views/admin/ListsAdmin.tsx index 8094e21..84173f5 100644 --- a/web/src/views/admin/ListsAdmin.tsx +++ b/web/src/views/admin/ListsAdmin.tsx @@ -1,9 +1,9 @@ import { useEffect, useMemo, useState } from "react"; import { useLocation } from "wouter"; import { ChevronLeft, ChevronRight, List, Plus, Search } from "lucide-react"; -import { can } from "@/lib/adminAccess"; -import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/adminDirectory"; -import { getLists, queryLists, type DirectoryList } from "@/lib/adminLists"; +import { can } from "@/lib/admin/adminAccess"; +import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/admin/adminDirectory"; +import { getLists, queryLists, type DirectoryList } from "@/lib/admin/adminLists"; import { plural, t } from "@/lib/i18n"; import { Empty, Spinner } from "@/ui/misc"; import { useSession } from "@/store/session"; diff --git a/web/src/views/admin/RoleSheet.tsx b/web/src/views/admin/RoleSheet.tsx index 91fc87e..edeed22 100644 --- a/web/src/views/admin/RoleSheet.tsx +++ b/web/src/views/admin/RoleSheet.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from "react"; import { Lock, Search, Trash2, X } from "lucide-react"; -import { can, canGrantRole } from "@/lib/adminAccess"; -import { describeDirectoryError } from "@/lib/adminDirectory"; -import { describeLinked, DomainError } from "@/lib/adminDomains"; +import { can, canGrantRole } from "@/lib/admin/adminAccess"; +import { describeDirectoryError } from "@/lib/admin/adminDirectory"; +import { describeLinked, DomainError } from "@/lib/admin/adminDomains"; import { canBuildOn, createRole, @@ -15,7 +15,7 @@ import { type DirectoryRole, type PermissionState, type RoleDefaults, -} from "@/lib/adminRoles"; +} from "@/lib/admin/adminRoles"; import type { PermissionEntry } from "@/lib/permissionLabels"; import { plural, t } from "@/lib/i18n"; import { Dialog } from "@/ui/dialog"; diff --git a/web/src/views/admin/RolesAdmin.tsx b/web/src/views/admin/RolesAdmin.tsx index 5301eee..da90a04 100644 --- a/web/src/views/admin/RolesAdmin.tsx +++ b/web/src/views/admin/RolesAdmin.tsx @@ -1,9 +1,9 @@ import { useEffect, useMemo, useState } from "react"; import { useLocation } from "wouter"; import { Plus, Search, ShieldCheck } from "lucide-react"; -import { can } from "@/lib/adminAccess"; -import { describeDirectoryError } from "@/lib/adminDirectory"; -import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/adminRoles"; +import { can } from "@/lib/admin/adminAccess"; +import { describeDirectoryError } from "@/lib/admin/adminDirectory"; +import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/admin/adminRoles"; import { describePermissions, loadPermissionCatalog, type PermissionEntry } from "@/lib/permissionLabels"; import { plural, t } from "@/lib/i18n"; import { Empty, Spinner } from "@/ui/misc"; diff --git a/web/src/views/admin/TenantSheet.tsx b/web/src/views/admin/TenantSheet.tsx index f6f7cbe..7d2ab6f 100644 --- a/web/src/views/admin/TenantSheet.tsx +++ b/web/src/views/admin/TenantSheet.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from "react"; import { Globe, Plus, Trash2, X } from "lucide-react"; -import { can, canGrantRole, type RoleDef } from "@/lib/adminAccess"; -import { describeDirectoryError } from "@/lib/adminDirectory"; -import { describeLinked, DomainError } from "@/lib/adminDomains"; +import { can, canGrantRole, type RoleDef } from "@/lib/admin/adminAccess"; +import { describeDirectoryError } from "@/lib/admin/adminDirectory"; +import { describeLinked, DomainError } from "@/lib/admin/adminDomains"; import { countTenantMembers, createTenant, @@ -19,7 +19,7 @@ import { type TenantMemberKind, type TenantQuota, type TenantRoles, -} from "@/lib/adminTenants"; +} from "@/lib/admin/adminTenants"; import { formatSize } from "@/lib/format"; import { proxiedImageUrl } from "@/lib/html"; import { plural, t } from "@/lib/i18n"; diff --git a/web/src/views/admin/TenantsAdmin.tsx b/web/src/views/admin/TenantsAdmin.tsx index 7c04677..e1fbf41 100644 --- a/web/src/views/admin/TenantsAdmin.tsx +++ b/web/src/views/admin/TenantsAdmin.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; import { useLocation } from "wouter"; import { Building2, ChevronLeft, ChevronRight, Plus, Search } from "lucide-react"; -import { can, type RoleDef } from "@/lib/adminAccess"; -import { describeDirectoryError, listRoles } from "@/lib/adminDirectory"; -import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/adminTenants"; +import { can, type RoleDef } from "@/lib/admin/adminAccess"; +import { describeDirectoryError, listRoles } from "@/lib/admin/adminDirectory"; +import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/admin/adminTenants"; import { formatSize } from "@/lib/format"; import { proxiedImageUrl } from "@/lib/html"; import { plural, t } from "@/lib/i18n"; diff --git a/web/src/views/admin/__tests__/account-sheet.test.tsx b/web/src/views/admin/__tests__/account-sheet.test.tsx index 9101189..d7c122f 100644 --- a/web/src/views/admin/__tests__/account-sheet.test.tsx +++ b/web/src/views/admin/__tests__/account-sheet.test.tsx @@ -5,7 +5,7 @@ import { Router } from "wouter"; import { memoryLocation } from "wouter/memory-location"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { DirectoryAccount } from "@/lib/adminDirectory"; +import type { DirectoryAccount } from "@/lib/admin/adminDirectory"; import { AccountSheet } from "../AccountSheet"; import type { DirectoryContext } from "../directoryContext"; diff --git a/web/src/views/admin/__tests__/admin-dashboard.test.tsx b/web/src/views/admin/__tests__/admin-dashboard.test.tsx index bdb150c..1593ce4 100644 --- a/web/src/views/admin/__tests__/admin-dashboard.test.tsx +++ b/web/src/views/admin/__tests__/admin-dashboard.test.tsx @@ -6,7 +6,7 @@ import { memoryLocation } from "wouter/memory-location"; import { JmapMethodError } from "@/jmap/client"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { MetricRecord } from "@/lib/adminDashboard"; +import type { MetricRecord } from "@/lib/admin/adminDashboard"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -15,8 +15,8 @@ const feeds = vi.hoisted(() => ({ metrics: (): Promise => Promise.resolve([]), })); -vi.mock("@/lib/adminDashboard", async (original) => ({ - ...(await original()), +vi.mock("@/lib/admin/adminDashboard", async (original) => ({ + ...(await original()), countObjects: vi.fn(async (object: string) => feeds.counts[object]), loadMetrics: vi.fn(() => feeds.metrics()), })); diff --git a/web/src/views/admin/__tests__/domain-sheet.test.tsx b/web/src/views/admin/__tests__/domain-sheet.test.tsx index 86b0c0c..109706c 100644 --- a/web/src/views/admin/__tests__/domain-sheet.test.tsx +++ b/web/src/views/admin/__tests__/domain-sheet.test.tsx @@ -17,8 +17,8 @@ const domain = { dnsZoneFile: 'example.com. IN MX 10 mail.example.com.\nexample.com. IN TXT "v=spf1 mx -all"\n', }; -vi.mock("@/lib/adminDomains", async (original) => ({ - ...(await original()), +vi.mock("@/lib/admin/adminDomains", async (original) => ({ + ...(await original()), getDomains: vi.fn(async () => [domain]), listDkimKeys: vi.fn(async () => [{ id: "k1", "@type": "Dkim1Ed25519Sha256", selector: "v1-ed25519", stage: "active" }]), })); diff --git a/web/src/views/admin/__tests__/group-sheet.test.tsx b/web/src/views/admin/__tests__/group-sheet.test.tsx index 078e602..1dbf294 100644 --- a/web/src/views/admin/__tests__/group-sheet.test.tsx +++ b/web/src/views/admin/__tests__/group-sheet.test.tsx @@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { DirectoryGroup } from "@/lib/adminGroups"; +import type { DirectoryGroup } from "@/lib/admin/adminGroups"; import type { DirectoryContext } from "../directoryContext"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -17,8 +17,8 @@ const api = vi.hoisted(() => ({ destroyGroup: vi.fn(async () => {}), })); -vi.mock("@/lib/adminGroups", async (original) => ({ - ...(await original()), +vi.mock("@/lib/admin/adminGroups", async (original) => ({ + ...(await original()), listMembers: vi.fn(async () => ({ members: api.members, total: api.members.length })), searchUsers: vi.fn(async () => []), setMembership: api.setMembership, diff --git a/web/src/views/admin/__tests__/list-sheet.test.tsx b/web/src/views/admin/__tests__/list-sheet.test.tsx index 23b727f..03c288b 100644 --- a/web/src/views/admin/__tests__/list-sheet.test.tsx +++ b/web/src/views/admin/__tests__/list-sheet.test.tsx @@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { DirectoryList } from "@/lib/adminLists"; +import type { DirectoryList } from "@/lib/admin/adminLists"; import type { DirectoryContext } from "../directoryContext"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const api = vi.hoisted(() => ({ updateList: vi.fn(async () => {}) })); -vi.mock("@/lib/adminLists", async (original) => ({ ...(await original()), updateList: api.updateList })); +vi.mock("@/lib/admin/adminLists", async (original) => ({ ...(await original()), updateList: api.updateList })); const { ListSheet } = await import("../ListSheet"); diff --git a/web/src/views/admin/__tests__/role-sheet.test.tsx b/web/src/views/admin/__tests__/role-sheet.test.tsx index dc35534..686dd97 100644 --- a/web/src/views/admin/__tests__/role-sheet.test.tsx +++ b/web/src/views/admin/__tests__/role-sheet.test.tsx @@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { DirectoryRole } from "@/lib/adminRoles"; +import type { DirectoryRole } from "@/lib/admin/adminRoles"; import type { PermissionEntry } from "@/lib/permissionLabels"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const api = vi.hoisted(() => ({ updateRole: vi.fn(async () => {}) })); -vi.mock("@/lib/adminRoles", async (original) => ({ ...(await original()), updateRole: api.updateRole })); +vi.mock("@/lib/admin/adminRoles", async (original) => ({ ...(await original()), updateRole: api.updateRole })); const { RoleSheet } = await import("../RoleSheet"); diff --git a/web/src/views/admin/__tests__/tenant-sheet.test.tsx b/web/src/views/admin/__tests__/tenant-sheet.test.tsx index ff49a1d..77b8acc 100644 --- a/web/src/views/admin/__tests__/tenant-sheet.test.tsx +++ b/web/src/views/admin/__tests__/tenant-sheet.test.tsx @@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useSession } from "@/store/session"; import type { JmapSession } from "@/jmap/types"; -import type { DirectoryTenant } from "@/lib/adminTenants"; +import type { DirectoryTenant } from "@/lib/admin/adminTenants"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -13,8 +13,8 @@ const api = vi.hoisted(() => ({ updateTenant: vi.fn(async () => {}), setDomainTenant: vi.fn(async () => {}), })); -vi.mock("@/lib/adminTenants", async (original) => ({ - ...(await original()), +vi.mock("@/lib/admin/adminTenants", async (original) => ({ + ...(await original()), countTenantMembers: vi.fn(async () => api.counts), tenantDomains: vi.fn(async () => ({ inTenant: [{ id: "d3", name: "old-brand.example" }], unassigned: [{ id: "d4", name: "spare.example" }] })), updateTenant: api.updateTenant, diff --git a/web/src/views/admin/__tests__/tenants-admin.test.tsx b/web/src/views/admin/__tests__/tenants-admin.test.tsx index ec5eafc..d9cd9ea 100644 --- a/web/src/views/admin/__tests__/tenants-admin.test.tsx +++ b/web/src/views/admin/__tests__/tenants-admin.test.tsx @@ -9,8 +9,8 @@ import type { JmapSession } from "@/jmap/types"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const api = vi.hoisted(() => ({ queryTenants: vi.fn(async () => ({ ids: ["t1"], total: 1 })) })); -vi.mock("@/lib/adminTenants", async (original) => ({ - ...(await original()), +vi.mock("@/lib/admin/adminTenants", async (original) => ({ + ...(await original()), queryTenants: api.queryTenants, getTenants: vi.fn(async () => [{ id: "t1", name: "Acme Corp", quotas: {}, usedDiskQuota: 0 }]), })); diff --git a/web/src/views/admin/directoryContext.ts b/web/src/views/admin/directoryContext.ts index 29919f5..a86fbb1 100644 --- a/web/src/views/admin/directoryContext.ts +++ b/web/src/views/admin/directoryContext.ts @@ -1,5 +1,5 @@ -import type { RoleDef } from "@/lib/adminAccess"; -import type { DirectoryAccount, DirectoryDomain } from "@/lib/adminDirectory"; +import type { RoleDef } from "@/lib/admin/adminAccess"; +import type { DirectoryAccount, DirectoryDomain } from "@/lib/admin/adminDirectory"; import { t } from "@/lib/i18n"; export interface DirectoryContext { diff --git a/web/src/views/admin/usePermissions.ts b/web/src/views/admin/usePermissions.ts index 17f96e2..4961db2 100644 --- a/web/src/views/admin/usePermissions.ts +++ b/web/src/views/admin/usePermissions.ts @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { useSession } from "@/store/session"; -import { permissionSet, type Permissions } from "@/lib/adminAccess"; +import { permissionSet, type Permissions } from "@/lib/admin/adminAccess"; /** * The signed-in account's permissions, as a set, stable between renders. diff --git a/web/src/views/calendar/CalendarView.tsx b/web/src/views/calendar/CalendarView.tsx index 2fca8a9..ff517a7 100644 --- a/web/src/views/calendar/CalendarView.tsx +++ b/web/src/views/calendar/CalendarView.tsx @@ -15,7 +15,7 @@ import type { Anchor } from "@/ui/popover"; import { CalendarContextMenu, eventColor, type CalendarContext } from "./CalendarContextMenu"; import { toast } from "@/ui/toast"; import { askEditScope, droppedMessage, runScoped } from "./scope"; -import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/eventDrag"; +import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/calendar/eventDrag"; import { t as translate } from "@/lib/i18n"; type View = "month" | "week" | "day" | "agenda"; diff --git a/web/src/views/calendar/EventEditor.tsx b/web/src/views/calendar/EventEditor.tsx index 24d55ec..d25ffbe 100644 --- a/web/src/views/calendar/EventEditor.tsx +++ b/web/src/views/calendar/EventEditor.tsx @@ -13,9 +13,9 @@ import { RecipientInput } from "../compose/RecipientInput"; import { DateField, DateTimeField } from "@/ui/datefield"; import { browserTimeZone, dateToZonedLocal, formatDuration, fromInputDateTime, listTimeZones, parseDuration, toInputDateTime, toLocalDateOnly, zonedToDate, DAY_MS, humanDuration } from "@/lib/dates"; import { formatClock, formatNumericDate, formatWeekday, formatWeekdayDate } from "@/lib/datetime"; -import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/recurrence"; +import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/calendar/recurrence"; import { newKey } from "@/lib/contacts"; -import { availabilityWindow } from "@/lib/availabilityWindow"; +import { availabilityWindow } from "@/lib/calendar/availabilityWindow"; import { askEditScope, droppedMessage, runScoped } from "./scope"; import { plural, t as translate } from "@/lib/i18n"; diff --git a/web/src/views/calendar/EventPopover.tsx b/web/src/views/calendar/EventPopover.tsx index e0c56b1..4269f22 100644 --- a/web/src/views/calendar/EventPopover.tsx +++ b/web/src/views/calendar/EventPopover.tsx @@ -6,7 +6,7 @@ import { confirmDialog } from "@/ui/dialog"; import { toast } from "@/ui/toast"; import { askDeleteScope, runScoped } from "./scope"; import { formatTimeRange, humanDuration, parseDuration } from "@/lib/dates"; -import { describeRule } from "@/lib/recurrence"; +import { describeRule } from "@/lib/calendar/recurrence"; import { useCompose } from "@/store/compose"; import { useSettings } from "@/store/settings"; import { categoryOf, eventColor } from "./CalendarContextMenu"; diff --git a/web/src/views/mail/MessageList.tsx b/web/src/views/mail/MessageList.tsx index 4715d10..82dae14 100644 --- a/web/src/views/mail/MessageList.tsx +++ b/web/src/views/mail/MessageList.tsx @@ -16,7 +16,7 @@ import { rowClick } from "@/lib/listSelection"; import { MenuItem, MenuSep, MenuTitle, Popover, useMenu } from "@/ui/popover"; import { useCompose } from "@/store/compose"; import { useCalendar } from "@/store/calendar"; -import { startAppointment } from "@/lib/appointment"; +import { startAppointment } from "@/lib/calendar/appointment"; import { toast } from "@/ui/toast"; import { haptic, usePullToRefresh, useTouchRow, PULL_TRIGGER } from "@/lib/touch"; import { describeSwipe, type SwipeAction, type SwipeDescriptor, type SwipeIcon } from "@/lib/swipe"; diff --git a/web/src/views/mail/MessageView.tsx b/web/src/views/mail/MessageView.tsx index 66d2be3..86cfd67 100644 --- a/web/src/views/mail/MessageView.tsx +++ b/web/src/views/mail/MessageView.tsx @@ -8,7 +8,7 @@ import { useSettings } from "@/store/settings"; import { draftFromMailto, useCompose } from "@/store/compose"; import { useContacts } from "@/store/contacts"; import { useCalendar } from "@/store/calendar"; -import { startAppointment } from "@/lib/appointment"; +import { startAppointment } from "@/lib/calendar/appointment"; import { client } from "@/jmap/client"; import { SignatureBanner } from "./SignatureBanner"; import { useSignature } from "@/lib/smime/useSignature";