diff --git a/src/modules/features/SettingDialogue/PanePatches.ts b/src/modules/features/SettingDialogue/PanePatches.ts index 9c813404..0a654692 100644 --- a/src/modules/features/SettingDialogue/PanePatches.ts +++ b/src/modules/features/SettingDialogue/PanePatches.ts @@ -9,7 +9,7 @@ import { Logger } from "@vrtmrz/livesync-commonlib/compat/common/logger"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import type { PageFunctions } from "./SettingPane.ts"; -import { visibleOnly } from "./SettingPane.ts"; +import { markSettingRowWithSubsequentButtons, markSubsequentButton, visibleOnly } from "./SettingPane.ts"; import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-browser"; import { ExtraSuffixIndexedDB } from "@vrtmrz/livesync-commonlib/compat/common/types"; import { migrateDatabases } from "./settingUtils.ts"; @@ -177,7 +177,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch"); }); void addPanel(paneEl, "Remediation").then((paneEl) => { - const setting = new Setting(paneEl).setClass("sls-setting-subsequent-buttons"); + const setting = markSettingRowWithSubsequentButtons(new Setting(paneEl)); const dateEl = setting.controlEl.createSpan(); setting .addText((text) => { @@ -215,6 +215,9 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen }) .setAuto("maxMTimeForReflectEvents") .addApplyButton(["maxMTimeForReflectEvents"]); + if (setting.applyButtonComponent) { + markSubsequentButton(setting.applyButtonComponent); + } this.addOnSaved("maxMTimeForReflectEvents", async (key) => { const buttons = ["Restart Now", "Later"] as const; diff --git a/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts b/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts index efc10bcf..5f357b6b 100644 --- a/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts +++ b/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts @@ -12,9 +12,11 @@ const remediationHarness = vi.hoisted(() => { onChange: vi.fn(), setValue: vi.fn(), }; + const addButtonClass = vi.fn(); const setClass = vi.fn(); return { + addButtonClass, createSpan, dateElement, inputEl, @@ -25,6 +27,11 @@ const remediationHarness = vi.hoisted(() => { vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class LiveSyncSetting { + applyButtonComponent = { + buttonEl: { + addClass: remediationHarness.addButtonClass, + }, + }; controlEl = { createSpan: remediationHarness.createSpan, }; @@ -100,6 +107,7 @@ describe("panePatches remediation setting", () => { expect(createSpan).not.toHaveBeenCalled(); expect(remediationHarness.createSpan).toHaveBeenCalledOnce(); expect(remediationHarness.dateElement.textContent).toBe("No limit configured"); - expect(remediationHarness.setClass).toHaveBeenCalledWith("sls-setting-subsequent-buttons"); + expect(remediationHarness.setClass).toHaveBeenCalledWith("sls-setting-row-with-subsequent-buttons"); + expect(remediationHarness.addButtonClass).toHaveBeenCalledWith("sls-setting-subsequent-button"); }); }); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index a8700ea2..98218f73 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -11,7 +11,12 @@ import { Menu, type ButtonComponent } from "@/deps.ts"; import { $msg } from "@/common/translation"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; -import { setButtonDestructiveState, type PageFunctions } from "./SettingPane.ts"; +import { + markSettingRowWithSubsequentButtons, + markSubsequentButton, + setButtonDestructiveState, + type PageFunctions, +} from "./SettingPane.ts"; // import { visibleOnly } from "./SettingPane.ts"; import InfoPanel from "./InfoPanel.svelte"; import { writable } from "svelte/store"; @@ -105,12 +110,10 @@ export function paneRemoteConfig( void addPanel(paneEl, "E2EE Configuration", () => {}).then((paneEl) => { const infoPanel = new SveltePanel(InfoPanel, paneEl, E2EESummaryWritable); this.lifetimeComponent.register(() => infoPanel.destroy()); - const setupButton = new Setting(paneEl) - .setName("Configure E2EE") - .setClass("sls-setting-subsequent-buttons"); + const setupButton = markSettingRowWithSubsequentButtons(new Setting(paneEl).setName("Configure E2EE")); setupButton .addButton((button) => - setButtonDestructiveState(button) + setButtonDestructiveState(markSubsequentButton(button)) .onClick(async () => { const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); @@ -120,7 +123,7 @@ export function paneRemoteConfig( .setButtonText("Configure") ) .addButton((button) => - setButtonDestructiveState(button) + setButtonDestructiveState(markSubsequentButton(button)) .onClick(async () => { const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts index 5417800c..edfd32d4 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; const runtime = vi.hoisted(() => ({ + buttonClasses: [] as string[], panels: [] as Array<{ destroy: ReturnType }>, settingClasses: [] as string[], })); @@ -36,7 +37,20 @@ vi.mock("./LiveSyncSetting.ts", () => ({ return this; } - addButton() { + addButton(callback: (button: unknown) => void) { + const button = { + buttonEl: { + addClass: (value: string) => runtime.buttonClasses.push(value), + classList: { toggle: vi.fn() }, + }, + onClick() { + return this; + }, + setButtonText() { + return this; + }, + }; + callback(button); return this; } @@ -91,6 +105,7 @@ function createPanelElement(): HTMLElement { } afterEach(() => { + runtime.buttonClasses.length = 0; runtime.panels.length = 0; runtime.settingClasses.length = 0; vi.clearAllMocks(); @@ -103,7 +118,13 @@ describe("paneRemoteConfig", () => { register: vi.fn((callback: () => unknown) => callbacks.push(callback)), unload: vi.fn(() => callbacks.splice(0).forEach((callback) => callback())), }; - const addPanel = vi.fn((_parent: HTMLElement, _heading: string) => Promise.resolve(createPanelElement())); + const addPanel = vi.fn((_parent: HTMLElement, heading: string) => ({ + then(callback: (paneEl: HTMLElement) => void) { + if (heading === "E2EE Configuration") { + callback(createPanelElement()); + } + }, + })); const host = { editingSettings: { remoteConfigurations: {} }, core: { settings: { remoteConfigurations: {} } }, @@ -112,7 +133,8 @@ describe("paneRemoteConfig", () => { paneRemoteConfig.call(host as never, {} as HTMLElement, { addPanel } as never); await vi.waitFor(() => expect(runtime.panels).toHaveLength(1)); - expect(runtime.settingClasses).toContain("sls-setting-subsequent-buttons"); + expect(runtime.settingClasses).toContain("sls-setting-row-with-subsequent-buttons"); + expect(runtime.buttonClasses).toEqual(["sls-setting-subsequent-button", "sls-setting-subsequent-button"]); lifetimeComponent.unload(); diff --git a/src/modules/features/SettingDialogue/SettingPane.ts b/src/modules/features/SettingDialogue/SettingPane.ts index a21a7e4c..4b789807 100644 --- a/src/modules/features/SettingDialogue/SettingPane.ts +++ b/src/modules/features/SettingDialogue/SettingPane.ts @@ -6,7 +6,7 @@ import { type ConfigLevel, } from "@vrtmrz/livesync-commonlib/compat/common/types"; import type { AllSettingItemKey, AllSettings } from "./settingConstants"; -import type { ButtonComponent } from "@/deps.ts"; +import type { ButtonComponent, Setting } from "@/deps.ts"; export const combineOnUpdate = (func1: OnUpdateFunc, func2: OnUpdateFunc): OnUpdateFunc => { return () => ({ @@ -58,6 +58,18 @@ export function setButtonDestructiveState(button: ButtonComponent, isDestructive return button; } +/** Marks a setting row whose selected action buttons may wrap onto separate lines. */ +export function markSettingRowWithSubsequentButtons(setting: T): T { + setting.setClass("sls-setting-row-with-subsequent-buttons"); + return setting; +} + +/** Marks an action button which may wrap onto a later line in its setting row. */ +export function markSubsequentButton(button: ButtonComponent): ButtonComponent { + button.buttonEl.addClass("sls-setting-subsequent-button"); + return button; +} + export function visibleOnly(cond: () => boolean): OnUpdateFunc { return () => ({ visibility: cond(), diff --git a/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts b/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts index be208eb8..d53be7f3 100644 --- a/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts +++ b/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts @@ -1,6 +1,6 @@ -import type { ButtonComponent } from "@/deps.ts"; +import type { ButtonComponent, Setting } from "@/deps.ts"; import { describe, expect, it, vi } from "vitest"; -import { setButtonDestructiveState } from "./SettingPane.ts"; +import { markSettingRowWithSubsequentButtons, markSubsequentButton, setButtonDestructiveState } from "./SettingPane.ts"; type CompatibleButton = ButtonComponent & { setDestructive?: () => ButtonComponent; @@ -10,6 +10,7 @@ type CompatibleButton = ButtonComponent & { function createButton(overrides: Partial = {}): CompatibleButton { return { buttonEl: { + addClass: vi.fn(), classList: { toggle: vi.fn(), }, @@ -18,6 +19,30 @@ function createButton(overrides: Partial = {}): CompatibleButt } as unknown as CompatibleButton; } +function createSetting(): Setting { + return { + setClass: vi.fn().mockReturnThis(), + } as unknown as Setting; +} + +describe("markSettingRowWithSubsequentButtons", () => { + it("marks only the supplied setting row as containing subsequent actions", () => { + const setting = createSetting(); + + expect(markSettingRowWithSubsequentButtons(setting)).toBe(setting); + expect(setting.setClass).toHaveBeenCalledWith("sls-setting-row-with-subsequent-buttons"); + }); +}); + +describe("markSubsequentButton", () => { + it("marks only the supplied button as a subsequent action", () => { + const button = createButton(); + + expect(markSubsequentButton(button)).toBe(button); + expect(button.buttonEl.addClass).toHaveBeenCalledWith("sls-setting-subsequent-button"); + }); +}); + describe("setButtonDestructiveState", () => { it("uses the native destructive-button API when it is available", () => { const setDestructive = vi.fn(); diff --git a/styles.css b/styles.css index 717253b1..1c9e2705 100644 --- a/styles.css +++ b/styles.css @@ -547,17 +547,17 @@ body.is-mobile .sls-setting button { white-space: normal; } -body.is-mobile .sls-setting-subsequent-buttons { +body.is-mobile .sls-setting-row-with-subsequent-buttons { flex-wrap: wrap; } -body.is-mobile .sls-setting-subsequent-buttons .setting-item-control { +body.is-mobile .sls-setting-row-with-subsequent-buttons .setting-item-control { min-width: 0; flex: 1 1 100%; flex-wrap: wrap; } -body.is-mobile .sls-setting-subsequent-buttons .setting-item-control > button { +body.is-mobile .sls-setting .sls-setting-subsequent-button { flex: 1 1 12rem; }