mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-05 15:58:19 +00:00
feat(settings): keep SyncGuard preferences in device-local storage
This commit is contained in:
1 parent
47828e934b
commit
9b47310a3c
6 files changed
+309
-36
No files matched your search
+41
-2
@@ -1,6 +1,7 @@
|
||||
import { MemoryValueStore } from "@vrtmrz/value-store";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SETTINGS } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { DEFAULT_SYNC_GUARD_SETTINGS, type SyncGuardSettings } from "@/modules/services/ObsidianSettingService.ts";
|
||||
import type { SettingDefinitionGroup, SettingDefinitionItem, SettingDefinitionPage } from "obsidian";
|
||||
import type { PageFunctions } from "./SettingPane.ts";
|
||||
|
||||
@@ -152,6 +153,7 @@ type SettingsTabOptions = {
|
||||
};
|
||||
|
||||
function createSettingsTab(options: SettingsTabOptions = {}): ObsidianLiveSyncSettingTab {
|
||||
const syncGuardSettings: SyncGuardSettings = { ...DEFAULT_SYNC_GUARD_SETTINGS };
|
||||
const core = {
|
||||
settings: { ...DEFAULT_SETTINGS, useAdvancedMode: true },
|
||||
confirm: {
|
||||
@@ -160,6 +162,10 @@ function createSettingsTab(options: SettingsTabOptions = {}): ObsidianLiveSyncSe
|
||||
services: {
|
||||
setting: {
|
||||
deviceValues: new MemoryValueStore(),
|
||||
syncGuardSettings,
|
||||
setSyncGuardSetting: vi.fn(async (key: keyof SyncGuardSettings, value: boolean | number) => {
|
||||
Object.assign(syncGuardSettings, { [key]: value });
|
||||
}),
|
||||
getDeviceAndVaultName: vi.fn(() => ""),
|
||||
saveSettingData: vi.fn(async () => undefined),
|
||||
},
|
||||
@@ -179,8 +185,8 @@ function createSettingsTab(options: SettingsTabOptions = {}): ObsidianLiveSyncSe
|
||||
};
|
||||
const tab = new ObsidianLiveSyncSettingTab({} as never, plugin as never);
|
||||
Object.assign(tab, {
|
||||
_editingSettings: { ...DEFAULT_SETTINGS, useAdvancedMode: true },
|
||||
initialSettings: { ...DEFAULT_SETTINGS, useAdvancedMode: true },
|
||||
_editingSettings: { ...DEFAULT_SETTINGS, ...syncGuardSettings, useAdvancedMode: true },
|
||||
initialSettings: { ...DEFAULT_SETTINGS, ...syncGuardSettings, useAdvancedMode: true },
|
||||
});
|
||||
return tab;
|
||||
}
|
||||
@@ -347,6 +353,39 @@ describe("ObsidianLiveSyncSettingTab native page lifecycle", () => {
|
||||
).toEqual(["useAdvancedMode", "usePowerUserMode", "useEdgeCaseMode"]);
|
||||
});
|
||||
|
||||
it("persists declarative SyncGuard controls through Obsidian's local settings service", async () => {
|
||||
const tab = createSettingsTab();
|
||||
const setting = tab.services.setting as typeof tab.services.setting & {
|
||||
setSyncGuardSetting: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
const initialCoreSettings = { ...tab.core.settings };
|
||||
|
||||
await tab.setControlValue("syncGuardEnabled", true);
|
||||
await tab.setControlValue("syncGuardMinimumFiles", 6);
|
||||
|
||||
expect(setting.setSyncGuardSetting).toHaveBeenNthCalledWith(1, "syncGuardEnabled", true);
|
||||
expect(setting.setSyncGuardSetting).toHaveBeenNthCalledWith(2, "syncGuardMinimumFiles", 6);
|
||||
expect(tab.getControlValue("syncGuardEnabled")).toBe(true);
|
||||
expect(tab.getControlValue("syncGuardMinimumFiles")).toBe(6);
|
||||
expect(tab.core.settings).toEqual(initialCoreSettings);
|
||||
expect(setting.saveSettingData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes active settings when local SyncGuard preferences change elsewhere", async () => {
|
||||
const tab = createSettingsTab();
|
||||
tab.isShown = true;
|
||||
const refresh = vi.spyOn(tab, "requestPageRefresh").mockImplementation(() => undefined);
|
||||
const setting = tab.services.setting as typeof tab.services.setting & { syncGuardSettings: SyncGuardSettings };
|
||||
setting.syncGuardSettings.syncGuardEnabled = true;
|
||||
setting.syncGuardSettings.syncGuardMinimumFiles = 13;
|
||||
|
||||
await tab.requestReload();
|
||||
|
||||
expect(tab.editingSettings.syncGuardEnabled).toBe(true);
|
||||
expect(tab.editingSettings.syncGuardMinimumFiles).toBe(13);
|
||||
expect(refresh).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("omits the old Setup child page and keeps standard General and Advanced pages native", () => {
|
||||
const tab = createSettingsTab();
|
||||
const pages = collectPages(tab.getSettingDefinitions());
|
||||
|
||||
@@ -77,6 +77,11 @@ import { createExtraMenuSettingSpecGroup, createGeneralSettingSpecGroups } from
|
||||
import { SetupManager } from "@/modules/features/SetupManager.ts";
|
||||
import { isP2PMainRemote } from "@/common/remoteConfiguration.ts";
|
||||
import { withOwnedRemoteResource } from "@/common/ownedRemoteResource.ts";
|
||||
import {
|
||||
getSyncGuardSettings,
|
||||
isSyncGuardSettingKey,
|
||||
setSyncGuardSetting,
|
||||
} from "@/modules/services/ObsidianSettingService.ts";
|
||||
|
||||
// For creating a document
|
||||
// const toc = new Set<string>();
|
||||
@@ -138,6 +143,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
if (
|
||||
this.isReconstructionRequiredSetting(k) ||
|
||||
this.isConfigurationEncryptionSetting(k) ||
|
||||
isSyncGuardSettingKey(k) ||
|
||||
k in OnDialogSettingsDefault
|
||||
) {
|
||||
// //@ts-ignore
|
||||
@@ -157,6 +163,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
(e) => this.isDirty(e) && !this.isReconstructionRequiredSetting(e)
|
||||
);
|
||||
await this.saveConfigurationEncryptionIfDirty(changedKeys);
|
||||
for (const key of changedKeys.filter(isSyncGuardSettingKey)) {
|
||||
await setSyncGuardSetting(this.services.setting, key, this.editingSettings[key]);
|
||||
this.copySettingValue(this.initialSettings, this.editingSettings, key);
|
||||
}
|
||||
this.applySetting(changedKeys);
|
||||
this.reloadAllSettings();
|
||||
}
|
||||
@@ -177,6 +187,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await this.saveConfigurationEncryptionIfDirty(appliedKeys);
|
||||
for (const k of appliedKeys) {
|
||||
if (this.isConfigurationEncryptionSetting(k)) continue;
|
||||
if (isSyncGuardSettingKey(k)) {
|
||||
await setSyncGuardSetting(this.services.setting, k, this.editingSettings[k]);
|
||||
this.copySettingValue(this.initialSettings, this.editingSettings, k);
|
||||
continue;
|
||||
}
|
||||
if (k in OnDialogSettingsDefault) {
|
||||
await this.saveLocalSetting(k as keyof OnDialogSettings);
|
||||
this.copySettingValue(this.initialSettings, this.editingSettings, k);
|
||||
@@ -307,7 +322,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
ret.configPassphrase = this.localConfigurationPassphrase;
|
||||
ret.preset = "";
|
||||
ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName();
|
||||
return ret;
|
||||
return { ...ret, ...getSyncGuardSettings(this.services.setting) };
|
||||
}
|
||||
computeAllLocalSettings(): Partial<OnDialogSettings> {
|
||||
const syncMode = this.editingSettings?.liveSync
|
||||
@@ -375,7 +390,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
return;
|
||||
}
|
||||
const localSetting = this.reloadAllLocalSettings();
|
||||
if (key in this.core.settings) {
|
||||
if (isSyncGuardSettingKey(key) || key in this.core.settings) {
|
||||
if (key in localSetting) {
|
||||
this.copySettingValue(this.initialSettings, localSetting, key);
|
||||
this.copySettingValue(this.editingSettings, localSetting, key);
|
||||
@@ -567,25 +582,25 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
const nativeTabIsShown =
|
||||
this.supportsDeclarativeSettings() && this.containerEl !== undefined && this.containerEl.isShown();
|
||||
if (this.isShown || nativeTabIsShown) {
|
||||
const newConf = this.core.settings;
|
||||
const keys = Object.keys(newConf) as (keyof ObsidianLiveSyncSettings)[];
|
||||
const newConf = { ...this.core.settings, ...getSyncGuardSettings(this.services.setting) };
|
||||
const keys = Object.keys(newConf) as AllSettingItemKey[];
|
||||
let hasLoaded = false;
|
||||
let catalogueVisibilityChanged = false;
|
||||
for (const k of keys) {
|
||||
if (isObjectDifferent(newConf[k], this.initialSettings?.[k])) {
|
||||
if (isObjectDifferent(Reflect.get(newConf, k), this.initialSettings?.[k])) {
|
||||
// Something has changed
|
||||
if (this.isDirty(k as AllSettingItemKey)) {
|
||||
if (this.isDirty(k)) {
|
||||
// And modified.
|
||||
this.core.confirm.askInPopup(
|
||||
`config-reloaded-${k}`,
|
||||
$msg("obsidianLiveSyncSettingTab.msgSettingModified", {
|
||||
setting: getConfName(k as AllSettingItemKey),
|
||||
setting: getConfName(k),
|
||||
}),
|
||||
(anchor) => {
|
||||
anchor.text = $msg("obsidianLiveSyncSettingTab.optionHere");
|
||||
anchor.addEventListener("click", () => {
|
||||
this.refreshSetting(k as AllSettingItemKey);
|
||||
if (this.changesPageCatalogue(k as AllSettingItemKey)) {
|
||||
this.refreshSetting(k);
|
||||
if (this.changesPageCatalogue(k)) {
|
||||
this.requestCatalogueRefresh();
|
||||
} else {
|
||||
this.requestPageRefresh();
|
||||
@@ -595,12 +610,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
);
|
||||
} else {
|
||||
// not modified
|
||||
this.refreshSetting(k as AllSettingItemKey);
|
||||
this.refreshSetting(k);
|
||||
if (k in OnDialogSettingsDefault) {
|
||||
continue;
|
||||
}
|
||||
hasLoaded = true;
|
||||
if (this.changesPageCatalogue(k as AllSettingItemKey)) {
|
||||
if (this.changesPageCatalogue(k)) {
|
||||
catalogueVisibilityChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,50 @@
|
||||
export {
|
||||
AllSettingDefault,
|
||||
OnDialogSettingsDefault,
|
||||
SettingInformation,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
export type {
|
||||
AllSettings,
|
||||
AllSettingItemKey,
|
||||
AllStringItemKey,
|
||||
AllNumericItemKey,
|
||||
AllBooleanItemKey,
|
||||
OnDialogSettings,
|
||||
ValueOf,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
export { OnDialogSettingsDefault } from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
export type { OnDialogSettings } from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
|
||||
import {
|
||||
AllSettingDefault as CommonlibAllSettingDefault,
|
||||
SettingInformation as CommonlibSettingInformation,
|
||||
getConfig as getCommonlibConfig,
|
||||
getConfName as getCommonlibConfName,
|
||||
type AllSettingItemKey,
|
||||
type AllSettings as CommonlibAllSettings,
|
||||
type AllSettingItemKey as CommonlibAllSettingItemKey,
|
||||
type AllStringItemKey as CommonlibAllStringItemKey,
|
||||
type AllNumericItemKey as CommonlibAllNumericItemKey,
|
||||
type AllBooleanItemKey as CommonlibAllBooleanItemKey,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/settingConstants";
|
||||
import type { ConfigurationItem } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { MessageTranslator } from "@vrtmrz/livesync-commonlib/context";
|
||||
import { translateLiveSyncMessage } from "@/common/translation";
|
||||
import {
|
||||
DEFAULT_SYNC_GUARD_SETTINGS,
|
||||
isSyncGuardSettingKey,
|
||||
type SyncGuardSettingKey,
|
||||
type SyncGuardSettings,
|
||||
} from "@/modules/services/ObsidianSettingService";
|
||||
|
||||
export type AllSettings = Omit<CommonlibAllSettings, SyncGuardSettingKey> & SyncGuardSettings;
|
||||
export type AllStringItemKey = CommonlibAllStringItemKey;
|
||||
export type AllNumericItemKey = Exclude<CommonlibAllNumericItemKey, SyncGuardSettingKey> | "syncGuardMinimumFiles";
|
||||
export type AllBooleanItemKey = Exclude<CommonlibAllBooleanItemKey, SyncGuardSettingKey> | "syncGuardEnabled";
|
||||
export type AllSettingItemKey = Exclude<CommonlibAllSettingItemKey, SyncGuardSettingKey> | SyncGuardSettingKey;
|
||||
export type ValueOf<T extends AllSettingItemKey> = AllSettings[T];
|
||||
|
||||
const syncGuardSettingInformation: Record<SyncGuardSettingKey, ConfigurationItem> = {
|
||||
syncGuardEnabled: {
|
||||
name: "Enable SyncGuard",
|
||||
desc: "Enable device-local SyncGuard protection during synchronisation.",
|
||||
},
|
||||
syncGuardMinimumFiles: {
|
||||
name: "SyncGuard minimum files",
|
||||
desc: "Minimum number of Vault files required for SyncGuard protection to apply.",
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSettingDefault = { ...CommonlibAllSettingDefault, ...DEFAULT_SYNC_GUARD_SETTINGS };
|
||||
export const SettingInformation: Partial<Record<AllSettingItemKey, ConfigurationItem>> = {
|
||||
...CommonlibSettingInformation,
|
||||
...syncGuardSettingInformation,
|
||||
};
|
||||
|
||||
// Commonlib defaults `translate` to its English-only translator, so every caller which omits
|
||||
// it silently renders English regardless of `displayLanguage`. Default it to the LiveSync
|
||||
@@ -28,10 +53,12 @@ import { translateLiveSyncMessage } from "@/common/translation";
|
||||
|
||||
/** `getConfig` with the LiveSync catalogue applied by default. */
|
||||
export function getConfig(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
|
||||
return getCommonlibConfig(key, translate);
|
||||
if (isSyncGuardSettingKey(key)) return syncGuardSettingInformation[key];
|
||||
return getCommonlibConfig(key as CommonlibAllSettingItemKey, translate);
|
||||
}
|
||||
|
||||
/** `getConfName` with the LiveSync catalogue applied by default. See `getConfig`. */
|
||||
export function getConfName(key: AllSettingItemKey, translate: MessageTranslator = translateLiveSyncMessage) {
|
||||
return getCommonlibConfName(key, translate);
|
||||
if (isSyncGuardSettingKey(key)) return syncGuardSettingInformation[key].name;
|
||||
return getCommonlibConfName(key as CommonlibAllSettingItemKey, translate);
|
||||
}
|
||||
Reference in new issue
Block a user