Group six more clusters out of web/src/lib
Takes the flat module count from 66 to 42, continuing what admin/ and
calendar/ started.
lib/mailbox/ archiveDate, emptyFolder, folderMove, labelTree,
mailboxName, mailboxRoute
lib/sieve/ sieve, sieveApply, sieveFolders
lib/input/ keyboard, swipe, touch, listSelection, dropUpload
lib/notify/ notify, webpush, webpushEnable
lib/sw/ swCache, swFacts, staleBuild
lib/text/ html, markdown, text, emlName
FOUR THINGS THE FILENAMES GET WRONG, each checked by reading the file
rather than trusting what it is called:
- appFolder is not a mailbox. It is the `ihasmail` folder in JMAP
*Files*, where the client keeps signature images and synced settings.
It stays flat.
- format holds no formatting of text. It re-exports the date and clock
formatters, so it belongs with dates/datetime, not with text/.
- preview is the file viewer deciding what it can show without
downloading, and source is where to point someone asking for this
instance's AGPL source. Neither is about text.
- notify is not Web Push. It is the tab title, the favicon badge and
the new-mail sound -- in-app notification, which is why it sits with
webpush rather than under sw/ with the service worker's own concerns.
threadScroll stays flat too: it decides where a conversation opens, which
is view state rather than a gesture, and input/ is honest only if
everything in it interprets something the reader did.
No behavior change. Almost every reference was on the @/ alias; eight
relative imports in files that did not move, or that moved away from a
sibling, needed rewriting by hand.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { tc } from "@/lib/i18n";
|
||||
import type { Mailbox } from "@/jmap/types";
|
||||
|
||||
/**
|
||||
* What to call a folder on screen.
|
||||
*
|
||||
* Stalwart names the standard folders once, when the account is created, in
|
||||
* whatever language the server was set up in — and never renames them
|
||||
* afterwards, because the name is stored data every other client has mapped.
|
||||
* So a German reader on an English-provisioned account sees "Deleted Items"
|
||||
* in an otherwise German app, and there is nothing the server can be asked to
|
||||
* do about it: the account locale exists in `x:AccountSettings`, but writing it
|
||||
* needs `sysAccountSettingsSet`, which the built-in user role does not carry.
|
||||
*
|
||||
* The role is the way out. JMAP tags the standard folders — `inbox`, `trash`,
|
||||
* `drafts` and the rest — and ihasmail already trusts the role rather than the
|
||||
* name everywhere it matters, so the display name can follow the interface
|
||||
* language without anything being written to the server.
|
||||
*
|
||||
* Only the roles. A folder somebody made and called "Newsletters" keeps that
|
||||
* name, because those are their words and translating them would be inventing
|
||||
* a folder they never made.
|
||||
*
|
||||
* The cost, and it is real: another client on the same account still shows
|
||||
* "Deleted Items", because that is what the folder is called. Within ihasmail
|
||||
* this stays consistent — everything that names a folder goes through here,
|
||||
* including the "moved to …" toast, which exists precisely so that message
|
||||
* does not name somewhere the reader cannot find.
|
||||
*/
|
||||
/*
|
||||
* Every one of these is translated in the "folder" context, including the
|
||||
* unambiguous ones. Two of them genuinely need it -- "Archive" is also the
|
||||
* button that archives, "Important" is also a priority tag, and German wants a
|
||||
* different word for each -- and applying it to only those two would leave the
|
||||
* next person to notice which. A context on all of them is one rule.
|
||||
*/
|
||||
const ROLE_NAMES: Record<string, () => string> = {
|
||||
inbox: () => tc("folder", "Inbox"),
|
||||
archive: () => tc("folder", "Archive"),
|
||||
drafts: () => tc("folder", "Drafts"),
|
||||
sent: () => tc("folder", "Sent"),
|
||||
trash: () => tc("folder", "Deleted Items"),
|
||||
junk: () => tc("folder", "Junk Mail"),
|
||||
important: () => tc("folder", "Important"),
|
||||
all: () => tc("folder", "All mail"),
|
||||
};
|
||||
|
||||
/** The folder's name as the reader should see it. */
|
||||
export function mailboxDisplayName(mailbox: { name: string; role?: string | null } | null | undefined): string {
|
||||
if (!mailbox) return "";
|
||||
const localized = mailbox.role ? ROLE_NAMES[mailbox.role] : undefined;
|
||||
return localized ? localized() : mailbox.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this folder's displayed name is ihasmail's rather than the server's.
|
||||
*
|
||||
* Anything that *edits* the name has to know: a rename dialog prefilled with
|
||||
* "Papierkorb" would rename the folder to that on the server the moment
|
||||
* somebody pressed Save, which is a real change made by accident to a folder
|
||||
* they were only looking at. Renaming a role folder is refused anyway, but
|
||||
* relying on that would be relying on a rule enforced somewhere else.
|
||||
*/
|
||||
export function isLocalizedName(mailbox: { role?: string | null } | null | undefined): boolean {
|
||||
return Boolean(mailbox?.role && mailbox.role in ROLE_NAMES);
|
||||
}
|
||||
|
||||
/** A path of folder names, for a picker that shows where a folder sits. */
|
||||
export function mailboxDisplayPath(mailbox: Mailbox, all: Record<string, Mailbox>): string {
|
||||
const parts: string[] = [];
|
||||
let cur: Mailbox | undefined = mailbox;
|
||||
const seen = new Set<string>();
|
||||
while (cur && !seen.has(cur.id)) {
|
||||
seen.add(cur.id);
|
||||
parts.unshift(mailboxDisplayName(cur));
|
||||
cur = cur.parentId ? all[cur.parentId] : undefined;
|
||||
}
|
||||
return parts.join(" / ");
|
||||
}
|
||||
Reference in New Issue
Block a user