Settings: a section per plugin, and call the folder Spam
Plugin settings pages were stacked at the bottom of Settings -> Accounts, which put antispam's thresholds and lists under a heading about mail accounts. Each `settings.page` now gets its own entry in the Settings nav: "Antispam filtering" and "Mail filters". Slot gains an optional `title` for this — the host needs a label when it gives a slot an area of its own, and falls back to the plugin name. PluginSlot gains an optional `plugin` filter so a host can render one plugin's bundle rather than every binding for a slot name. Wording: the app calls that folder Spam (i18n `nav.spam`), so the plugin's copy now does too — "filed into Spam", "Move to Spam", "marked as spam". The IMAP special-use stays `junk`, which is what the protocol calls it; only what the user reads changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018SqJYt7jD7oUEkoY68sBZo
This commit is contained in:
@@ -11,6 +11,7 @@ import { useMe } from "../../lib/auth";
|
||||
import { useTheme } from "../../lib/theme";
|
||||
import { setTimeFormat } from "../../lib/timeFormat";
|
||||
import { PluginSlot } from "../../plugins/PluginSlot";
|
||||
import { usePluginSettingsSections } from "../../plugins/manifest";
|
||||
import { AccountWizard } from "../wizard/AccountWizard";
|
||||
|
||||
const SECTIONS = ["profile", "security", "accounts", "calendars", "appearance", "integrations"] as const;
|
||||
@@ -29,10 +30,14 @@ export function Settings() {
|
||||
// The add-account wizard renders inside this layout (route /settings/accounts/new),
|
||||
// with the Accounts nav item highlighted.
|
||||
const addingAccount = useLocation().pathname.endsWith("/accounts/new");
|
||||
// Plugins contributing `settings.page` each get their own nav entry, keyed by
|
||||
// plugin name, rather than being stacked at the bottom of Accounts.
|
||||
const pluginSections = usePluginSettingsSections();
|
||||
const isPluginSection = pluginSections.some((b) => b.plugin === params.section);
|
||||
const section = (
|
||||
addingAccount
|
||||
? "accounts"
|
||||
: SECTIONS.includes(params.section as Section)
|
||||
: SECTIONS.includes(params.section as Section) || isPluginSection
|
||||
? params.section
|
||||
: "profile"
|
||||
) as Section;
|
||||
@@ -58,6 +63,19 @@ export function Settings() {
|
||||
{t(`settings.${s}` as const)}
|
||||
</Link>
|
||||
))}
|
||||
{pluginSections.map((b) => (
|
||||
<Link
|
||||
key={b.plugin}
|
||||
to={`/settings/${b.plugin}${fromQuery}`}
|
||||
className={`block rounded px-3 py-2 text-sm ${
|
||||
section === (b.plugin as Section)
|
||||
? "bg-app-active font-medium text-app-fg"
|
||||
: "hover:bg-app-hover"
|
||||
}`}
|
||||
>
|
||||
{b.title ?? b.plugin}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
{addingAccount && <AccountWizard embedded onDone={() => navigate("/settings/accounts")} />}
|
||||
@@ -67,11 +85,11 @@ export function Settings() {
|
||||
{section === "calendars" && <CalendarsSection />}
|
||||
{section === "appearance" && <AppearanceSection />}
|
||||
{section === "integrations" && <IntegrationsSection />}
|
||||
{/* Plugin-contributed settings UI (SPEC §11.3 settings.page), under Accounts.
|
||||
Matches the accounts section width (max-w-5xl) with a gap above it. */}
|
||||
{!addingAccount && section === "accounts" && (
|
||||
<div className="mt-8 max-w-5xl">
|
||||
<PluginSlot name="settings.page" />
|
||||
{/* Plugin-contributed settings UI (SPEC §11.3 settings.page): one section
|
||||
per plugin, selected from the nav like any built-in section. */}
|
||||
{isPluginSection && (
|
||||
<div className="max-w-5xl">
|
||||
<PluginSlot name="settings.page" plugin={section} />
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user