From f7712b1c1ec302c032e4412b9e6556e00a997770 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Tue, 15 Sep 2026 22:44:53 -0700 Subject: [PATCH] Group the admin and calendar modules, and stop calling screenshots docs web/src/lib had grown to 85 flat modules -- 42% of the web source, about 12,800 lines -- with one subdirectory (smime/) to its name. The tell was that a naming prefix had taken over a directory's job: eight adminX.ts files sat adjacent because alphabetical order put them there, not because anything said they belonged together. lib/admin/ adminAccess, adminDashboard, adminDirectory, adminDomains, adminGroups, adminLists, adminRoles, adminTenants lib/calendar/ appointment, availabilityWindow, eventDrag, ics, recurrence Tests move with their modules into lib/admin/__tests__ and lib/calendar/__tests__, which is what views/ already does. describeRules stays in lib/__tests__: it checks that sieve's describeRule and recurrence's agree, so it belongs to neither. recurrence.ts joins the calendar group and archiveDate.ts does not, which is the opposite of the first guess from the filenames. archiveDate picks the Archive/2026/09 mailbox for a message -- mail, not calendar -- while recurrence reads JSCalendarRecurrenceRule. schedule.ts is scheduled *send*, so it stays put too. birthdays.ts is left alone deliberately: it is read off the contact cards and only rendered by the calendar, so it belongs to whichever of the two you ask. docs/ held no documentation. It held ten JPEGs and the two scripts that capture them, while the actual documentation is a separate site in the ihasmail.org repository -- so anyone opening docs/ expecting prose found a headless-Chrome driver. The images are now screenshots/, and the two capture scripts join the other .mjs tooling in scripts/, which is where a generator belongs. Renaming docs/ to screenshots/ wholesale would have produced screenshots/screenshots/inbox-dark.jpg. No behavior changes: every import was already on the @/ alias, so this is path rewrites and nothing else. --- README.md | 6 +++--- {docs/screenshots => screenshots}/calendar.jpg | Bin {docs/screenshots => screenshots}/compose.jpg | Bin {docs/screenshots => screenshots}/contacts.jpg | Bin {docs/screenshots => screenshots}/files.jpg | Bin {docs/screenshots => screenshots}/filters.jpg | Bin {docs/screenshots => screenshots}/inbox-dark.jpg | Bin {docs/screenshots => screenshots}/inbox-light.jpg | Bin {docs/screenshots => screenshots}/login.jpg | Bin {docs/screenshots => screenshots}/mobile.jpg | Bin {docs/screenshots => screenshots}/recipients.jpg | Bin {docs => scripts}/screenshots-light.mjs | 0 {docs => scripts}/screenshots.mjs | 8 ++++---- web/src/lib/__tests__/describeRules.test.ts | 2 +- .../lib/{ => admin}/__tests__/adminAccess.test.ts | 2 +- .../{ => admin}/__tests__/adminDashboard.test.ts | 2 +- .../{ => admin}/__tests__/adminDirectory.test.ts | 2 +- .../lib/{ => admin}/__tests__/adminDomains.test.ts | 2 +- .../lib/{ => admin}/__tests__/adminGroups.test.ts | 2 +- .../lib/{ => admin}/__tests__/adminLists.test.ts | 2 +- .../lib/{ => admin}/__tests__/adminRoles.test.ts | 4 ++-- .../lib/{ => admin}/__tests__/adminTenants.test.ts | 2 +- web/src/lib/{ => admin}/adminAccess.ts | 0 web/src/lib/{ => admin}/adminDashboard.ts | 0 web/src/lib/{ => admin}/adminDirectory.ts | 2 +- web/src/lib/{ => admin}/adminDomains.ts | 2 +- web/src/lib/{ => admin}/adminGroups.ts | 4 ++-- web/src/lib/{ => admin}/adminLists.ts | 2 +- web/src/lib/{ => admin}/adminRoles.ts | 6 +++--- web/src/lib/{ => admin}/adminTenants.ts | 4 ++-- .../{ => calendar}/__tests__/appointment.test.ts | 2 +- .../__tests__/availabilityWindow.test.ts | 2 +- .../lib/{ => calendar}/__tests__/eventDrag.test.ts | 2 +- web/src/lib/{ => calendar}/__tests__/ics.test.ts | 2 +- .../lib/{ => calendar}/__tests__/icsWrite.test.ts | 2 +- web/src/lib/{ => calendar}/appointment.ts | 6 +++--- web/src/lib/{ => calendar}/availabilityWindow.ts | 0 web/src/lib/{ => calendar}/eventDrag.ts | 4 ++-- web/src/lib/{ => calendar}/ics.ts | 0 web/src/lib/{ => calendar}/recurrence.ts | 2 +- web/src/store/calendar.ts | 2 +- web/src/views/AppShell.tsx | 2 +- web/src/views/admin/AccountSheet.tsx | 4 ++-- web/src/views/admin/AccountsAdmin.tsx | 6 +++--- web/src/views/admin/AdminDashboard.tsx | 4 ++-- web/src/views/admin/AdminNav.tsx | 2 +- web/src/views/admin/AdminView.tsx | 2 +- web/src/views/admin/DomainSheet.tsx | 6 +++--- web/src/views/admin/DomainsAdmin.tsx | 6 +++--- web/src/views/admin/GroupSheet.tsx | 6 +++--- web/src/views/admin/GroupsAdmin.tsx | 6 +++--- web/src/views/admin/ListSheet.tsx | 6 +++--- web/src/views/admin/ListsAdmin.tsx | 6 +++--- web/src/views/admin/RoleSheet.tsx | 8 ++++---- web/src/views/admin/RolesAdmin.tsx | 6 +++--- web/src/views/admin/TenantSheet.tsx | 8 ++++---- web/src/views/admin/TenantsAdmin.tsx | 6 +++--- .../views/admin/__tests__/account-sheet.test.tsx | 2 +- .../views/admin/__tests__/admin-dashboard.test.tsx | 6 +++--- web/src/views/admin/__tests__/domain-sheet.test.tsx | 4 ++-- web/src/views/admin/__tests__/group-sheet.test.tsx | 6 +++--- web/src/views/admin/__tests__/list-sheet.test.tsx | 4 ++-- web/src/views/admin/__tests__/role-sheet.test.tsx | 4 ++-- web/src/views/admin/__tests__/tenant-sheet.test.tsx | 6 +++--- .../views/admin/__tests__/tenants-admin.test.tsx | 4 ++-- web/src/views/admin/directoryContext.ts | 4 ++-- web/src/views/admin/usePermissions.ts | 2 +- web/src/views/calendar/CalendarView.tsx | 2 +- web/src/views/calendar/EventEditor.tsx | 4 ++-- web/src/views/calendar/EventPopover.tsx | 2 +- web/src/views/mail/MessageList.tsx | 2 +- web/src/views/mail/MessageView.tsx | 2 +- 72 files changed, 108 insertions(+), 108 deletions(-) rename {docs/screenshots => screenshots}/calendar.jpg (100%) rename {docs/screenshots => screenshots}/compose.jpg (100%) rename {docs/screenshots => screenshots}/contacts.jpg (100%) rename {docs/screenshots => screenshots}/files.jpg (100%) rename {docs/screenshots => screenshots}/filters.jpg (100%) rename {docs/screenshots => screenshots}/inbox-dark.jpg (100%) rename {docs/screenshots => screenshots}/inbox-light.jpg (100%) rename {docs/screenshots => screenshots}/login.jpg (100%) rename {docs/screenshots => screenshots}/mobile.jpg (100%) rename {docs/screenshots => screenshots}/recipients.jpg (100%) rename {docs => scripts}/screenshots-light.mjs (100%) rename {docs => scripts}/screenshots.mjs (98%) rename web/src/lib/{ => admin}/__tests__/adminAccess.test.ts (98%) rename web/src/lib/{ => admin}/__tests__/adminDashboard.test.ts (98%) rename web/src/lib/{ => admin}/__tests__/adminDirectory.test.ts (99%) rename web/src/lib/{ => admin}/__tests__/adminDomains.test.ts (98%) rename web/src/lib/{ => admin}/__tests__/adminGroups.test.ts (98%) rename web/src/lib/{ => admin}/__tests__/adminLists.test.ts (98%) rename web/src/lib/{ => admin}/__tests__/adminRoles.test.ts (94%) rename web/src/lib/{ => admin}/__tests__/adminTenants.test.ts (98%) rename web/src/lib/{ => admin}/adminAccess.ts (100%) rename web/src/lib/{ => admin}/adminDashboard.ts (100%) rename web/src/lib/{ => admin}/adminDirectory.ts (99%) rename web/src/lib/{ => admin}/adminDomains.ts (99%) rename web/src/lib/{ => admin}/adminGroups.ts (98%) rename web/src/lib/{ => admin}/adminLists.ts (98%) rename web/src/lib/{ => admin}/adminRoles.ts (97%) rename web/src/lib/{ => admin}/adminTenants.ts (98%) rename web/src/lib/{ => calendar}/__tests__/appointment.test.ts (98%) rename web/src/lib/{ => calendar}/__tests__/availabilityWindow.test.ts (98%) rename web/src/lib/{ => calendar}/__tests__/eventDrag.test.ts (99%) rename web/src/lib/{ => calendar}/__tests__/ics.test.ts (99%) rename web/src/lib/{ => calendar}/__tests__/icsWrite.test.ts (99%) rename web/src/lib/{ => calendar}/appointment.ts (96%) rename web/src/lib/{ => calendar}/availabilityWindow.ts (100%) rename web/src/lib/{ => calendar}/eventDrag.ts (98%) rename web/src/lib/{ => calendar}/ics.ts (100%) rename web/src/lib/{ => calendar}/recurrence.ts (99%) 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";