From edeac6f7e2413e3e245d667f0036cdd1940defed Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 26 Aug 2026 05:49:37 +0000 Subject: [PATCH] Separate settings component style helpers --- .../SettingDialogue/LiveSyncSetting.ts | 3 +- .../features/SettingDialogue/PaneHatch.ts | 3 +- .../SettingDialogue/PaneMaintenance.ts | 3 +- .../features/SettingDialogue/PanePatches.ts | 7 ++-- .../SettingDialogue/PanePatches.unit.spec.ts | 29 +++++++------ .../SettingDialogue/PaneRemoteConfig.ts | 14 +++---- .../PaneRemoteConfig.unit.spec.ts | 26 ++++++++---- .../features/SettingDialogue/SettingPane.ts | 32 --------------- .../SettingDialogue/settingComponentStyles.ts | 35 ++++++++++++++++ ...ts => settingComponentStyles.unit.spec.ts} | 41 ++++++++++++++----- styles.css | 6 +-- 11 files changed, 119 insertions(+), 80 deletions(-) create mode 100644 src/modules/features/SettingDialogue/settingComponentStyles.ts rename src/modules/features/SettingDialogue/{SettingPane.unit.spec.ts => settingComponentStyles.unit.spec.ts} (55%) diff --git a/src/modules/features/SettingDialogue/LiveSyncSetting.ts b/src/modules/features/SettingDialogue/LiveSyncSetting.ts index adaf66cb..bed51bd0 100644 --- a/src/modules/features/SettingDialogue/LiveSyncSetting.ts +++ b/src/modules/features/SettingDialogue/LiveSyncSetting.ts @@ -24,7 +24,8 @@ import { type AllBooleanItemKey, } from "./settingConstants.ts"; import { $msg } from "@/common/translation"; -import { setButtonDestructiveState, wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts"; +import { wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts"; +import { setButtonDestructiveState } from "./settingComponentStyles.ts"; export class LiveSyncSetting extends Setting { autoWiredComponent?: TextComponent | ToggleComponent | DropdownComponent | ButtonComponent | TextAreaComponent; diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index 376157ec..d14dfd08 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -24,7 +24,8 @@ import { import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync.ts"; import { EVENT_REQUEST_SHOW_HISTORY } from "@/common/obsidianEvents.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; -import { setButtonDestructiveState, type PageFunctions } from "./SettingPane.ts"; +import type { PageFunctions } from "./SettingPane.ts"; +import { setButtonDestructiveState } from "./settingComponentStyles.ts"; import { isNotFoundError } from "@vrtmrz/livesync-commonlib/compat/common/utils.doc"; import { chooseAndCopyFileDatabaseInfo, diff --git a/src/modules/features/SettingDialogue/PaneMaintenance.ts b/src/modules/features/SettingDialogue/PaneMaintenance.ts index d79a2b8c..a58825d6 100644 --- a/src/modules/features/SettingDialogue/PaneMaintenance.ts +++ b/src/modules/features/SettingDialogue/PaneMaintenance.ts @@ -10,7 +10,8 @@ import { fireAndForget } from "@vrtmrz/livesync-commonlib/compat/common/utils"; import { LiveSyncCouchDBReplicator } from "@vrtmrz/livesync-commonlib/compat/replication/couchdb/LiveSyncReplicator"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab"; -import { setButtonDestructiveState, visibleOnly, type PageFunctions } from "./SettingPane"; +import { visibleOnly, type PageFunctions } from "./SettingPane"; +import { setButtonDestructiveState } from "./settingComponentStyles.ts"; export function paneMaintenance( this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, diff --git a/src/modules/features/SettingDialogue/PanePatches.ts b/src/modules/features/SettingDialogue/PanePatches.ts index 0a654692..87a08c21 100644 --- a/src/modules/features/SettingDialogue/PanePatches.ts +++ b/src/modules/features/SettingDialogue/PanePatches.ts @@ -9,7 +9,8 @@ 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 { markSettingRowWithSubsequentButtons, markSubsequentButton, visibleOnly } from "./SettingPane.ts"; +import { visibleOnly } from "./SettingPane.ts"; +import { setButtonAdditionalActionState, setSettingAdditionalActionsState } from "./settingComponentStyles.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 +178,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch"); }); void addPanel(paneEl, "Remediation").then((paneEl) => { - const setting = markSettingRowWithSubsequentButtons(new Setting(paneEl)); + const setting = setSettingAdditionalActionsState(new Setting(paneEl)); const dateEl = setting.controlEl.createSpan(); setting .addText((text) => { @@ -216,7 +217,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen .setAuto("maxMTimeForReflectEvents") .addApplyButton(["maxMTimeForReflectEvents"]); if (setting.applyButtonComponent) { - markSubsequentButton(setting.applyButtonComponent); + setButtonAdditionalActionState(setting.applyButtonComponent); } this.addOnSaved("maxMTimeForReflectEvents", async (key) => { diff --git a/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts b/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts index 5f357b6b..bcdf5904 100644 --- a/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts +++ b/src/modules/features/SettingDialogue/PanePatches.unit.spec.ts @@ -12,15 +12,15 @@ const remediationHarness = vi.hoisted(() => { onChange: vi.fn(), setValue: vi.fn(), }; - const addButtonClass = vi.fn(); - const setClass = vi.fn(); + const setButtonClassState = vi.fn(); + const setSettingClassState = vi.fn(); return { - addButtonClass, createSpan, dateElement, inputEl, - setClass, + setButtonClassState, + setSettingClassState, textComponent, }; }); @@ -29,7 +29,14 @@ vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class LiveSyncSetting { applyButtonComponent = { buttonEl: { - addClass: remediationHarness.addButtonClass, + classList: { + toggle: remediationHarness.setButtonClassState, + }, + }, + }; + settingEl = { + classList: { + toggle: remediationHarness.setSettingClassState, }, }; controlEl = { @@ -45,11 +52,6 @@ vi.mock("./LiveSyncSetting.ts", () => ({ return this; } - setClass(value: string): this { - remediationHarness.setClass(value); - return this; - } - addApplyButton(): this { return this; } @@ -107,7 +109,10 @@ 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-row-with-subsequent-buttons"); - expect(remediationHarness.addButtonClass).toHaveBeenCalledWith("sls-setting-subsequent-button"); + expect(remediationHarness.setSettingClassState).toHaveBeenCalledWith( + "sls-setting-with-additional-actions", + true + ); + expect(remediationHarness.setButtonClassState).toHaveBeenCalledWith("sls-setting-additional-action", true); }); }); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 98218f73..68732138 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -12,11 +12,11 @@ import { $msg } from "@/common/translation"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import { - markSettingRowWithSubsequentButtons, - markSubsequentButton, + setButtonAdditionalActionState, setButtonDestructiveState, - type PageFunctions, -} from "./SettingPane.ts"; + setSettingAdditionalActionsState, +} from "./settingComponentStyles.ts"; +import type { PageFunctions } from "./SettingPane.ts"; // import { visibleOnly } from "./SettingPane.ts"; import InfoPanel from "./InfoPanel.svelte"; import { writable } from "svelte/store"; @@ -110,10 +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 = markSettingRowWithSubsequentButtons(new Setting(paneEl).setName("Configure E2EE")); + const setupButton = setSettingAdditionalActionsState(new Setting(paneEl).setName("Configure E2EE")); setupButton .addButton((button) => - setButtonDestructiveState(markSubsequentButton(button)) + setButtonDestructiveState(button) .onClick(async () => { const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); @@ -123,7 +123,7 @@ export function paneRemoteConfig( .setButtonText("Configure") ) .addButton((button) => - setButtonDestructiveState(markSubsequentButton(button)) + setButtonDestructiveState(setButtonAdditionalActionState(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 edfd32d4..d61a998a 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.unit.spec.ts @@ -23,6 +23,13 @@ vi.mock("@/common/translation", () => ({ vi.mock("./LiveSyncSetting.ts", () => ({ LiveSyncSetting: class { nameEl = { addClass: vi.fn(), appendText: vi.fn() }; + settingEl = { + classList: { + toggle: (value: string, enabled: boolean) => { + if (enabled) runtime.settingClasses.push(value); + }, + }, + }; setName() { return this; @@ -32,16 +39,17 @@ vi.mock("./LiveSyncSetting.ts", () => ({ return this; } - setClass(value: string) { - runtime.settingClasses.push(value); - return this; - } - addButton(callback: (button: unknown) => void) { const button = { buttonEl: { - addClass: (value: string) => runtime.buttonClasses.push(value), - classList: { toggle: vi.fn() }, + classList: { + toggle: (value: string, enabled: boolean) => { + if (enabled) runtime.buttonClasses.push(value); + }, + }, + }, + setDestructive() { + return this; }, onClick() { return this; @@ -133,8 +141,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-row-with-subsequent-buttons"); - expect(runtime.buttonClasses).toEqual(["sls-setting-subsequent-button", "sls-setting-subsequent-button"]); + expect(runtime.settingClasses).toContain("sls-setting-with-additional-actions"); + expect(runtime.buttonClasses).toEqual(["sls-setting-additional-action"]); lifetimeComponent.unload(); diff --git a/src/modules/features/SettingDialogue/SettingPane.ts b/src/modules/features/SettingDialogue/SettingPane.ts index 4b789807..442c2f93 100644 --- a/src/modules/features/SettingDialogue/SettingPane.ts +++ b/src/modules/features/SettingDialogue/SettingPane.ts @@ -6,7 +6,6 @@ import { type ConfigLevel, } from "@vrtmrz/livesync-commonlib/compat/common/types"; import type { AllSettingItemKey, AllSettings } from "./settingConstants"; -import type { ButtonComponent, Setting } from "@/deps.ts"; export const combineOnUpdate = (func1: OnUpdateFunc, func2: OnUpdateFunc): OnUpdateFunc => { return () => ({ @@ -39,37 +38,6 @@ export function setStyle(el: HTMLElement, styleHead: string, condition: () => bo } } -/** - * Applies destructive-action styling without requiring Obsidian 1.13 at - * runtime. Older supported versions used the `mod-warning` class for the same - * presentation. - */ -export function setButtonDestructiveState(button: ButtonComponent, isDestructive = true): ButtonComponent { - const compatibleButton = button as unknown as { - setDestructive?: () => ButtonComponent; - removeDestructive?: () => ButtonComponent; - }; - const updateNativeStyle = isDestructive ? compatibleButton.setDestructive : compatibleButton.removeDestructive; - if (typeof updateNativeStyle === "function") { - updateNativeStyle.call(button); - } else { - button.buttonEl.classList.toggle("mod-warning", 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/settingComponentStyles.ts b/src/modules/features/SettingDialogue/settingComponentStyles.ts new file mode 100644 index 00000000..f5bf7d1b --- /dev/null +++ b/src/modules/features/SettingDialogue/settingComponentStyles.ts @@ -0,0 +1,35 @@ +import type { ButtonComponent, Setting } from "@/deps.ts"; + +const SETTING_WITH_ADDITIONAL_ACTIONS_CLASS = "sls-setting-with-additional-actions"; +const ADDITIONAL_ACTION_CLASS = "sls-setting-additional-action"; + +/** + * Applies destructive-action styling without requiring Obsidian 1.13 at + * runtime. Older supported versions used the `mod-warning` class for the same + * presentation. + */ +export function setButtonDestructiveState(button: T, isDestructive = true): T { + const compatibleButton = button as unknown as { + setDestructive?: () => ButtonComponent; + removeDestructive?: () => ButtonComponent; + }; + const updateNativeStyle = isDestructive ? compatibleButton.setDestructive : compatibleButton.removeDestructive; + if (typeof updateNativeStyle === "function") { + updateNativeStyle.call(button); + } else { + button.buttonEl.classList.toggle("mod-warning", isDestructive); + } + return button; +} + +/** Sets whether a setting row contains actions which may move onto a later line. */ +export function setSettingAdditionalActionsState(setting: T, hasAdditionalActions = true): T { + setting.settingEl.classList.toggle(SETTING_WITH_ADDITIONAL_ACTIONS_CLASS, hasAdditionalActions); + return setting; +} + +/** Sets whether a button is an additional action which may move onto a later line. */ +export function setButtonAdditionalActionState(button: T, isAdditionalAction = true): T { + button.buttonEl.classList.toggle(ADDITIONAL_ACTION_CLASS, isAdditionalAction); + return button; +} diff --git a/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts b/src/modules/features/SettingDialogue/settingComponentStyles.unit.spec.ts similarity index 55% rename from src/modules/features/SettingDialogue/SettingPane.unit.spec.ts rename to src/modules/features/SettingDialogue/settingComponentStyles.unit.spec.ts index d53be7f3..e8344678 100644 --- a/src/modules/features/SettingDialogue/SettingPane.unit.spec.ts +++ b/src/modules/features/SettingDialogue/settingComponentStyles.unit.spec.ts @@ -1,6 +1,10 @@ import type { ButtonComponent, Setting } from "@/deps.ts"; import { describe, expect, it, vi } from "vitest"; -import { markSettingRowWithSubsequentButtons, markSubsequentButton, setButtonDestructiveState } from "./SettingPane.ts"; +import { + setButtonAdditionalActionState, + setButtonDestructiveState, + setSettingAdditionalActionsState, +} from "./settingComponentStyles.ts"; type CompatibleButton = ButtonComponent & { setDestructive?: () => ButtonComponent; @@ -10,7 +14,6 @@ type CompatibleButton = ButtonComponent & { function createButton(overrides: Partial = {}): CompatibleButton { return { buttonEl: { - addClass: vi.fn(), classList: { toggle: vi.fn(), }, @@ -21,25 +24,41 @@ function createButton(overrides: Partial = {}): CompatibleButt function createSetting(): Setting { return { - setClass: vi.fn().mockReturnThis(), + settingEl: { + classList: { + toggle: vi.fn(), + }, + }, } as unknown as Setting; } -describe("markSettingRowWithSubsequentButtons", () => { - it("marks only the supplied setting row as containing subsequent actions", () => { +describe("setSettingAdditionalActionsState", () => { + it("sets whether the supplied setting row contains additional actions", () => { const setting = createSetting(); - expect(markSettingRowWithSubsequentButtons(setting)).toBe(setting); - expect(setting.setClass).toHaveBeenCalledWith("sls-setting-row-with-subsequent-buttons"); + expect(setSettingAdditionalActionsState(setting, true)).toBe(setting); + expect(setSettingAdditionalActionsState(setting, false)).toBe(setting); + expect(setting.settingEl.classList.toggle).toHaveBeenNthCalledWith( + 1, + "sls-setting-with-additional-actions", + true + ); + expect(setting.settingEl.classList.toggle).toHaveBeenNthCalledWith( + 2, + "sls-setting-with-additional-actions", + false + ); }); }); -describe("markSubsequentButton", () => { - it("marks only the supplied button as a subsequent action", () => { +describe("setButtonAdditionalActionState", () => { + it("sets whether the supplied button is an additional action", () => { const button = createButton(); - expect(markSubsequentButton(button)).toBe(button); - expect(button.buttonEl.addClass).toHaveBeenCalledWith("sls-setting-subsequent-button"); + expect(setButtonAdditionalActionState(button, true)).toBe(button); + expect(setButtonAdditionalActionState(button, false)).toBe(button); + expect(button.buttonEl.classList.toggle).toHaveBeenNthCalledWith(1, "sls-setting-additional-action", true); + expect(button.buttonEl.classList.toggle).toHaveBeenNthCalledWith(2, "sls-setting-additional-action", false); }); }); diff --git a/styles.css b/styles.css index 1c9e2705..6788414f 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-row-with-subsequent-buttons { +body.is-mobile .sls-setting-with-additional-actions { flex-wrap: wrap; } -body.is-mobile .sls-setting-row-with-subsequent-buttons .setting-item-control { +body.is-mobile .sls-setting-with-additional-actions .setting-item-control { min-width: 0; flex: 1 1 100%; flex-wrap: wrap; } -body.is-mobile .sls-setting .sls-setting-subsequent-button { +body.is-mobile .sls-setting .sls-setting-additional-action { flex: 1 1 12rem; }