Merge branch 'main' into feat/palettes
# Conflicts: # web/src/store/settings.ts
This commit is contained in:
@@ -2,6 +2,7 @@ import { create } from "zustand";
|
||||
import type { FolderRef } from "@/lib/sieveFolders";
|
||||
import { SPAM_HEADER_PROPS } from "@/lib/spamScore";
|
||||
import { groupByArchivePath, archivePath, type ArchiveGranularity } from "@/lib/archiveDate";
|
||||
import { isOptionalSort, withoutOptionalSorts } from "@/lib/listSort";
|
||||
import { JmapMethodError, chunk, client, setErrorMessage } from "@/jmap/client";
|
||||
import type {
|
||||
Comparator,
|
||||
@@ -1129,7 +1130,45 @@ function sortIdentities(list: Identity[], accountId: Id): Identity[] {
|
||||
return [...list].sort((a, b) => (a.id === pref ? -1 : b.id === pref ? 1 : a.email.localeCompare(b.email)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort properties a server has already refused, so it is asked once and not
|
||||
* once per folder for the rest of the session.
|
||||
*
|
||||
* Keyed by nothing: a refusal is about the server, and there is only one.
|
||||
*/
|
||||
let sortRefused = false;
|
||||
|
||||
async function runQuery(accountId: Id, q: ListQuery, position: number, limit: number) {
|
||||
/*
|
||||
* `hasKeyword` is an optional sort in RFC 8621, and a server that will not
|
||||
* do it fails the whole query rather than degrading it -- so "unread first"
|
||||
* on such a server means a folder that does not open at all, which is a
|
||||
* worse outcome than one in the wrong order.
|
||||
*
|
||||
* The refusal is caught once, the optional levels dropped, and the query
|
||||
* retried. Nothing is said the first time: the reader asked for an order and
|
||||
* got the closest the server can give, and a toast on every folder change
|
||||
* would be the app complaining about its own request.
|
||||
*/
|
||||
const query = sortRefused ? { ...q, sort: withoutOptionalSorts(q.sort) } : q;
|
||||
try {
|
||||
return await runQueryOnce(accountId, query, position, limit);
|
||||
} catch (err) {
|
||||
const optional = query.sort.some(isOptionalSort);
|
||||
if (!optional || !isUnsupportedSort(err)) throw err;
|
||||
sortRefused = true;
|
||||
return await runQueryOnce(accountId, { ...q, sort: withoutOptionalSorts(q.sort) }, position, limit);
|
||||
}
|
||||
}
|
||||
|
||||
/** The error a server raises for a sort property it does not implement. */
|
||||
function isUnsupportedSort(err: unknown): boolean {
|
||||
const type = (err as { type?: string } | null)?.type;
|
||||
const message = String((err as Error | null)?.message ?? "");
|
||||
return type === "unsupportedSort" || /unsupportedSort/i.test(message);
|
||||
}
|
||||
|
||||
async function runQueryOnce(accountId: Id, q: ListQuery, position: number, limit: number) {
|
||||
const calls: Array<[string, Record<string, unknown>, string]> = [
|
||||
["Email/query", { accountId, filter: q.filter, sort: q.sort, collapseThreads: q.collapseThreads, position, limit, calculateTotal: true }, "q"],
|
||||
["Email/get", { accountId, "#ids": { resultOf: "q", name: "Email/query", path: "/ids" }, properties: LIST_PROPS }, "e"],
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { create } from "zustand";
|
||||
import { hasCachedJson, loadJson, saveJson } from "@/lib/storage";
|
||||
import { effectiveMode, legacyTheme, migrateTheme, type Mode, type PaletteId } from "@/lib/palette";
|
||||
import type { SortLevel, SortPreset } from "@/lib/listSort";
|
||||
import { pendingSettingsKeys, queueSettingsPush } from "@/lib/settingsSync";
|
||||
import { setDateTimePrefs, setUiLanguageForFormatting, type DateFormat, type TimeFormat } from "@/lib/datetime";
|
||||
import type { SwipeAction } from "@/lib/swipe";
|
||||
@@ -151,6 +152,15 @@ export interface Settings {
|
||||
* dates nobody put there is a surprise rather than a feature.
|
||||
*/
|
||||
birthdayCalendar: boolean;
|
||||
/** What order the message list is in. See lib/listSort.ts. */
|
||||
listSortPreset: SortPreset;
|
||||
listSortLevels: SortLevel[];
|
||||
/**
|
||||
* Which folders it covers. Inbox-only is the useful default rather than a
|
||||
* timid one: unread-first is what people want where they triage, and
|
||||
* confusing in Sent, where everything is read.
|
||||
*/
|
||||
listSortScope: "inbox" | "all";
|
||||
fontSize: "small" | "medium" | "large";
|
||||
templates: Template[];
|
||||
labels: Label[];
|
||||
@@ -257,6 +267,9 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
timeZone: null,
|
||||
labelsSidebar: true,
|
||||
birthdayCalendar: false,
|
||||
listSortPreset: "newest",
|
||||
listSortLevels: [],
|
||||
listSortScope: "inbox",
|
||||
fontSize: "medium",
|
||||
templates: [],
|
||||
labels: [],
|
||||
|
||||
Reference in New Issue
Block a user